C++多线程编程(6)合作与同步
C++多线程编程(6)
合作与同步
1.数据竞争(data race)/竞争条件(race condition):当两个或更多线程或进程试图同时修改同一个共享、可修改数据块时。为了让竞争条件存在,目标内存块必须是可修改的,而且线程必须试图同时访问这个块,至少其中一个线程试图修改这个内存块。
创建竞争条件是多线程或并发编程的主要缺陷之一,另一个缺陷就是死锁。
2.死锁:每个线程轮流排他性访问变量或对象,以解释线程之间可能的竞争条件,可以提供一种lock()机制来实现。lock()机制使锁定的变量只能被首先调用lock()机制的线程访问,即一个线程A先锁定资源(变量或对象等),另一个线程B就无法锁定该资源,此时线程A和线程B互相等待对方释放资源,这种情况称作死锁(deadlock)。当锁定、挂起或冻结多个线程时,相互间等待对方释放资源,此时存在死锁。
解决途径:多个线程和多个任务同步与合作使用资源。
3.同步:允许多个线程或进程同时激活,同时共享资源而不干扰对方的操作。同步进程临时序列化多个程和任务的执行,防止竞争条件或死锁。
三种同步类型:数据同步、设备同步、任务同步。
4.同步关系:SS、FS、SF、FF
5.进程同步机制:信号量、条件变量、临界区
块或控制对一些设备的访问。充当共享资源的一种钥匙,这把钥匙只能由一个线程或进程拥有,其他进程
或线程必须等待资源被释放。具有此信号量的线程就被认为占有此信号量,任何试图占有这个信号量的其
他线程或进程一般都被阻塞,一直等到信号量的占有者释放它为止。
负整数值出现,执行操作:P,V,lock(),unlock(),wait(),down(),up())
信号量操作是原子性或不可分割性的操作。一旦开始了信号量操作,它就保证在不被抢占的情况下运行到
完成。程序员不能用普通布尔变量和布尔操作取代特殊的信号量变量和信号量操作,因为大部分操作不是
原子性的,它可能被中断。
6.信号量类型(3种):
这些机制的用途:防止竞争条件和死锁。
7.临界区:共享数据块被修改,或者共享资源被更改的区域,是创建竞争条件和死锁之地。如果正确管理,
就可以避免使用并发技术时可能发生的大部分缺陷。
8.互斥:保护程序中的临界区,将它们锁定,只允许一个线程或进程在某一时刻访问它,不让其他线程或进程访问。实现互斥机制通常称互斥量(mutex)。
互斥信号量包含实际可行信号量机制的所有必需操作:5种。
a.初始化(分配内存并赋予内存初始值,决定信号量是否被占有、私有或共享)
b.锁定请求(互斥量被锁定时导致获得锁定的线程具有该互斥量的占有权,其他未经授权的线程均被阻塞)
c.Try锁定(测试互斥量,检查互斥量的占有权。如果互斥量被占有没有阻塞线程,则返回错误;如果没有
被占有,则锁定成功。可以等待互斥量一段时间,在这段时间内,它未取消锁定,则线程继续执行)
d.取消锁定(互斥量释放或取消锁定请求将导致其他线程等待该互斥量的取消锁定,当其中一个线程获得
了互斥量的占有权时,所有剩下的线程都仍然等待访问权的再次阻塞)
e.析构(释放与互斥量相关的内存。如果互斥量被占有或某线程等待着该互斥量,可以销毁或关闭内存)
9.互斥量初始化:同步变量不会像常规变量一样保证得到初始化,互斥量初始化可能也会失败,原因有多
种。在锁定、取消锁定或销毁操作中使用任何信号量之前,确保信号量最初正确初始化了。互斥量初始化
通常由某种类型的函数调用来完成。
//POSIX互斥量初始化
pthread_mutex_t MHandle
pthread_mutex_init(&MHandle,Null);
//OS2互斥量初始化
HMTX MHandle
DosCreateMutexsem(NULL,&MHandle,0,0);
10.自愿互斥量策略:自愿调用pthread_mutex_lock来保护临界区某些线程对象的状态免遭破坏。使用互斥量访问同步化是一种编程策略,根据实际来实施。自愿使用互斥量来同步可能是多线程环境中主要缺陷产生的原因,程序员可能没有意识到保护临界区正被访问,这个问题可以通过C++中的封装技术来解决。
11.互斥信号量try_lock请求:为了防止线程在已被占有的互斥量上一直阻塞,调用try_lock()互斥量函数测试该互斥量的占有权。如果已被占有,返回一个错误,但不会阻塞线程。如果没有被占有,则锁定成功。
12.互斥信号量取消锁定(unlock)请求:只有互斥量的占有者可以取消锁定或释放互斥量。一旦线程获得互斥量占有权,仍希望访问互斥量的所有剩余线程再次被阻塞。如果同时还有其他线程在该互斥量上阻塞,则具有最高优先级的线程交付这个互斥量。如所有阻塞线程都具有相同优先权,线程按FIFO方式获取可用互斥量的访问权。
13.互斥信号量析构:释放或取消锁定互斥量不会释放与互斥量关联的内存。释放只是放弃互斥量的占有权,使其他占有者可以锁定他。如果要释放互斥量内存,必须销毁或关闭互斥量。如果互斥量仍被占有或存在阻塞线程还在等待该互斥量的释放,就不能销毁或关闭该互斥量。
【程序演示】互斥量的应用:
14.事件互斥量和条件变量:支持一种线程间的广播机制,允许一个线程广播给另一个线程告知某事件已经发生。当一个线程锁定了一个事件互斥量,它将阻塞,直到它接到了一条广播,告知它可以继续为止。
Win32环境: CreateEvent() 创建事件信号量对象(或命名事件信号量对象);OpenEvent()名字用作它的参数,从一个无关进程打开一个事件信号量对象;SetEvent() 事件信号量对象状态更改为被通知,如已通知,函数将无作用;ResetEvent()重置事件信号量对象,更改对象状态为被通知;PulseEvent()将对象状态更改为被通知,释放所有等待线程,然后将对象状态改为未被通知;WaitForSingleObject() 导致线程一直等待到对象被通知,将对象指定为参数之一;WaitForMultipleObjects() 导致线程等待一个包含一个或多个同步对象的数组。
【注意】每种环境中都必须创建事件互斥量,然后可以执行3个主要操作:事件互斥量等待、延后和销毁。互斥量和事件互斥量(或条件变量)区别:互斥量只是导致一个线程一直阻塞到该互斥量被释放为止;而事件互斥量让一个或多个线程一直等到条件满足为止,或等到一个事件或多个事件的发生。
【程序演示】 搜索匹配关键字列表的文本文件(TXT),如果搜到是文本文件则添加到文件队列中,如果从文件队列中搜索到了需要的关键字,那么从文件队列中删除该文件。这时候,需要等待“文本文件添加到文件队列中”这个事件的发生才能锁定该事件互斥量并进行下一步文本文件搜索匹配关键字。
15.无限延迟(indefinite postponement):死锁的特例(单进程死锁),非锁定资源所产生的,线程可能等待着某个事件的发生,或某个条件的满足,但情况却是事件永远也不会发生或者条件永远不会满足。
16.避免竞争条件:4种
17.死锁必须的先行条件:4个
18.解决死锁:应用定时互斥量、事件变量、同步变量以及信号量等这些定时同步机制.也可以通过使用C++ IPC(进程间控制)、ITC组件以及多线程架构,可以去掉应用程序中的竞争条件和死锁。