C++多线程编程(10)
类行为和线程处理
1.C++对象4种作用域类型:局部作用域、函数作用域、文件作用域、类作用域
程序分成多个进程,每个进程有自己的文本、数据和堆栈片段。每个程序有自己的堆。为了让进城访问另
一个进程的数据或命令,必须进行进程间通信。进程间通信位于每个进程外部,并且充当进程间数据流的
通道。
局部:只能在声明它所在块内部使用的名字,活声明点后所包含的块内使用。函数的形参名字具有此函数
最外部块的作用域
函数:只具有函数作用域的标签。
文件:只在块和类外部声明的文件作用域名字。具有文件作用域的名字为全局性的。
类:只能用于在此类的成员函数中,类的某对象用于(.)运算符之后,用于指向它的类或派生类的某对象
指针(->)运算符之后或应用于它的类或派生类作用域分辨率运算符(::)之后。在友元函数中声明的名字首
先属于全局作用域。对于在返回值或参数类型中声明的名字类也是如此。
2.程序每个源文件称一个翻译单元,在一个翻译单元中具有文件作用域的对象可以具有内部链接或外部连
接。内部连接只能再声明它的翻译单元中访问它,外部连接可以被其他翻译单元中的函数和对象访问。
线程需要参数来访问对象的条件,和线程可以通过参数自由访问对象的条件:
文件: 外部连接 无必须参数,内部连接 作为参数传递
局部:外部连接 无参数,内部连接作为参数传递
类:外部连接 无参数,内部连接作为参数传递
函数:外部连接 无参数,内部连接作为参数传递
值传递对象 可以从进程内的任一个线程单向访问任何一个对象,接收线程只能读取对象不能修改它。
引用或指针传递 则对象充当线程间的通信连接,且双向通信。
3.线程和类作用域: 如果声明对象具有文件作用域和外部连接,则线程可以访问对象的所有公有成员函
数,不必借助其他特殊手段;如果声明对象具有文件作用域和内部连接,而且线程执行的函数与对象位于
相同的翻译单元,则线程可以访问对象,也不必借助其他特殊手段。在其他任何场合,必须在包含原始对
象的线程和对象成员函数创建线程间进行参数传递。一旦创建了线程,对象访问就由对象作用域和连接所
控制。
4.4种基本同步关系:SS、FS、SF、FF
对象线程间两种依赖性关系:通信依赖性、同步依赖性。
a.通信依赖性:对象创建的线程试图并发修改对象的临界区时发生通信依赖性关系,线程间机制使用互
斥量来防止线程破坏对象临界区。
b.同步依赖性:当多个线程为共同的目标而工作,每个线程解决问题的一部分,则发生同步依赖性关系
。同步化对象的线程将使用条件变量、等待函数或事件互斥量的线程间机制。
这些机制是对象的一部分,通过继承或复合包含在对象中。每个对象都必须包含它的同步机制。
5.使用6个基本组件实现计算器服务器:宿主类、线程类、互斥量和事件类、友元成员函数、域类、强制
转换基本元素。
class threaded_calculator:private mutex{
private:
String InputString;
double Result;
list Tokens;
public:
threaded_calculator(void);
friend void evaluate(void *X);
friend void parse(void *X);
double evaluate(String Input);
list parse(String Input);
};
double threaded_calculator::evaluate(String Input)
{ ct_thread Thread;
double Temp;
lock();
Thread.begin(::evaluate,this);
Thread.wait();
Temp=Result;
unlock();
return(Temp);
}
6.多线程环境中构建和析构对象:
exit():如果不存在结构化的异常处理,推荐使用exit(),系统尝试清空缓冲器释放资源并在可能的地方
调用析构函数。如果析构函数中存在锁定或释放机制,则调用exit()常会让挂起的析构函数执行。
abort():所有执行都被取消。如果存在还没调用的析构函数,执行abort()请求后他们不被调用。如果这
些析构函数包含对一些当前占有互斥量的取消锁定或释放机制,则应用程序将退出并保持对互斥量的占有
权,其他所有等待该互斥量的进程或线程可能被无限延迟。
在多线程环境中强烈推荐使用C++异常处理机制,程序失败或抛出异常时,结构化异常处理让程序员有机
会决定通过同步机制如何应付。
异步执行时,并不能确保单个进程中的多个线程将采取并发或同时执行。
7. 构造函数和SS关系:直到构建另一个对象后,该对象才能被构建。如果对象间处于分离线程中,那么
需要使用条件变量(广播)或事件互斥量来管理对象间的SS关系。
一旦构建同步后,两个对象之间立即开始某种行为,确保每个对象包含自己的事件互斥量来管理诸如
SF\FS\SS关系的对象。
void event_mutex::postEvent(void) //生产者的构造函数使用发送事件信号
{ DosPostEventSem(EventMutex);
}
void event_mutex::waitEvent(void) //消费者的构造函数使用等待事件信号
{ DosWaitEventSem(EventMutex,EventWait);
}
8.析构函数和FF关系:
不同线程中依赖对象间的FF关系:对象B依赖于对象A,不希望在析构对象B之前析构对象A。
取消线程: pthread_cancel(ThreadId);//POSIX
、TerminateThread(ThreadId,ExitCode);//Win 32、
DosKillThread(ThreadId); //OS/2
有时线程取消不可避免,由于某些原因,一个线程被锁定,而且不能对任何通信尝试做出反应,这样的线
程必须取消。陷入致命或无限循环的线程必须取消。不过,在大部分清空下,应当使用条件变量和事件互
斥量来控制其活动部再需要的线程。
防止杂乱线程取消的第一步:在某个对象中封装线程句柄。第二步:使用C++异常处理机制try...catch{}
和throw{}块结构来支持异常处理。
#include
typedef void *(*FunctionPtr)(void*);
class ct_thread{
private:
pthread_t
ThreadId;
pthread_attr_t *Attr;
public:
ct_thread(void);
ct_thread(phtread_attr_t *Attribute);
~ct_thread(void);
void
begin(FunctionPtr PFN,void *X);
void
wait(void);
pthread_t
threadHandle(void);
pthread_t
threaded(void);
void
cancel(void); //取消线程,只有宿主类对象私有性地取消线程,不会担心cancel调用中使用
线程的ThreadId,因为ThreadId由ct_thread类控制。
};
9.使用C++异常处理机制:
a.调用堆栈和线程—— 允许异常沿着函数的调用链向上传播直到找到捕获该异常的处理器为止:
funcA()
{
try{ func
B() //最终捕获funcD中抛出的异常ErrorTypeA
}catch(ErrorTypeA X)
{
X.doSomething();
}catch(ErrorTypeB X)
{
X.doSomething();
}
}
funcB()
{
try{
funcC() //只处理ErrorTypeB
funcD() //沿调用链向上传播,找到处理ErrorTypeA异常的处理器
}catch(ErrorTypeB X)
{
X.doSomething();
}
}
funcC()
{
throw
ErrorTypeB
}
funcD()
{
throw ErrorTypeA
}
注意:每个线程应当负责自己的异常处理,在设计异常处理逻辑时,请单独考虑每个线程的调用堆栈。不要试图从一个线程到另一个线程抛出异常,因为进程中的每个线程都有属于自己的调用堆栈。
b.通过异常处理返回多种类型:允许函数返回不同的类型。
Date currentDate(String Day)
{ Date TempDay;
if(!validDate(Day))
{ throw Exception;//
正常取回一个Date对象,如不顺利抛出了异常,用作其他安排
}
TempDay(Day);
return TempDay;
}
c.可靠线程化架构:
可以借助异常处理机制来识别和处理死锁:进程中的每个线程都至少有一个异常处理器。当构造函数和析构函数为使用多线程应用程序中的部分对象时,他们应当包含异常抛出逻辑。判断死锁的架构部分应当在碰到死锁时,标识死锁情形的逻辑,抛出异常处理器可以强迫从两个线程移除资源,可以决定取消两个线程,从整体上保持系统的执行,处理完死锁后,处理器可以执行一些用户自定义的可选路径。
10.线程安全函数:
如果函数是不可重复进入(代码块不能在使用时更改),或访问未保护全局变量,或者包含静态可修改变量,则函数就是非线程安全的。反之则为线程安全函数或可重复进入的函数。
11.多线程环境中的不安全函数:
如果不知道来自库的哪一个函数时安全的,哪一个是不安全的,则有3种选择:
a.不要在应用程序中使用任何不安全函数
b.限制在单线程中使用所有不安全函数
c.通过一套同步机制封装所有的可能不安全函数。(提倡所有即将使用于多线程应用程序中的不安全函数提供接口类,一旦将不安全函数封装在接口类中,接口类或通过继承或通过复合可以与适当的同步对象结合在一起。然后通过继承或复合,接口类可以与它的宿主类结合,这样有效地消除了出线竞争条件的可能性。)
12.通过复合结合容器类与接口类,使用成员函数封装STL算法:
lqueue类:
template
class lqueue:virtual private named_mutex,virtual private
event_mutex}
protected:
deque SafeQueue;
public:
lquqe(char &MName,int Own,char *EName,int
Initial,unsigned long Dur);
inline void insert(T X);
inline T remove(void);
inline T front(void);
inline T back(void);
inline unsigned int empty(void);
inline unsigned int size(void);
inline void erase(void);
inline void reversed(void); //封装了STL算法,通常是一个自由漂浮的函数,非任何类的成员函数,可以被正确定义了迭代器的类自由使用。
void wait(void);
void broadCast(void);
}