1. 竞争条件
当两个或更多线程或进程试图同时修改同一个共享、可修改数据块时,这种情况称做竞争条件或数据竞争(data race)。
区别共享可修改数据与只读数据是重要的,区别多线程试图修改数据块与多线程只试图读取数据也是重要的。为了让竞争条件存中,目标内存块必须是可修改的,而且线程必须试图同时访问这个块,至少其中一个线程试图修改这个内存块。
当锁定、挂起或冻结多个线程时,相互间等待对方释放某些资源,此时存在死锁。竞争条件和死锁大概是多线程处理应用的最大缺陷。多个线程和多个任务必须同步化阻止竞争条件。我们必须找到一种让多个线程和多个任务合作使用资源避免死锁的途径。
同步允许多个线程或进程同时激活,同时共享资源而不干扰对方的操作。同步进程临时序列化多个线程和任务的执行,防止竞争条件或死锁。序列化仅在必须对硬件或软件资源按“一次一个”的方式访问时才发生。太多的序列化会削弱多线程的优势。
2. 同步关系
共有四种基本同步关系:
SS(start-start):线程B启动后线程A才能启动。线程B可以在线程A启动的同时或之前启动,但决不能在其之后启动;
FS(Finish-start):线程A完成一定的任务后,线程B才能启动;
SF(start-finish):线程B完成一定的任务后,线程A才能启动;
FF(finish-finish):线程B完成后,线程A才能完成,线程A可以在线程B之后完成,但不能在其之前完成。
3. 为了有效处理多个进程或线程间的竞争条件,有如下几种同步机制可以使用:
a. 信号量(semaphore); b. 条件变量(conditional variable); c. 临界区(critical section)。
信号量是一个受保护的变量,仅能被非常具体的操作来访问。它用于帮助线程或进程同步访问一些共享、可修改内存块,或者控制对一些设备的访问。它用来充当共享资源的一种钥匙,在某个时刻只能由一个线程或进程拥有,一旦拥有了它就可以锁定资源进行排它性地使用,在允许资源的进一步访问之前,其它进程或线程必须等待资源被释放。对信号量的操作(P, V, Lock 和 Unlock)。一种信号量类型为二进制信号量(binary semaphore),只能是0或1,如mutex,在其上只有P/V两种操作。另一种信号量是计数信号量(counting semaphore),只以非负整数值出现。
信号量操作具有原子性(atomicity)和不可分割性(indivisibility),它确保是不可侵害的,一旦启动了信号量操作,就不能由于任何原因被OS抢占。
如下是几种最常见的信号量类型:
a. 互斥信号量:帮助在代码临界区(初始化、锁定请求、try块、取消锁定、析构)实现互斥的机制;
b. 事件互斥量和条件变量:用于支持线程间的广播机制。允许一个线程向其它线程广播通知某事件的发生。当线程锁定某事件互斥量时,它将阻塞到接到广播(等待、延迟、销毁)为止;
c. 等待多个条件:与事件互斥量相同,但它扩展到包括多个事件或条件(等待、延迟、销毁)。
在多线程编程中的第一道防线是保护程序中的临界区,将它们锁定,不让其它线程或进程访问。这种保护临界区,只允许一个线程或进程在某一时刻访问它的过程称做互斥(mutual exclusion)。
对互斥量的初始化操作:初始化进程将分配保存信号量所需要的内存,并赋予内存初始值。它还决定信号量是否被占有、非占有、私有或共享。析构操作则释放与互斥量相关的内存。如果此时互斥量被占有或某线程仍然等待着该互斥量,可以销毁或关闭内存。
互斥信号量有如下可设置属性:
被占有或未被占有:指定设置了信号量初始状态的一个标志。如果为被占有,请求该信号量的其它所有线程都将阻塞。一旦OS创建了它,调用线程将立即占有该信号量,同时可以访问此资源。如果它没有被任何其它线程占有,则请求此信号量任一线程马上可以获得占有权。
命名或匿名:命名信号量总是共享的,知道它名字的所有进程都可以使用它。当应用程序在创建信号量时指定一个名字则OS将为它创建一个带名信号量。如果没有指定名字,则创建一个匿名信号量。
私有或共享:私有信号量未命名,通过它们的句柄来识别,只能被进程内的线程使用。共享信号量通过使用其名字或句柄可以被多进程中的线程所使用。
当存在多个线程等待着一个互斥量时,必须基于优先权和使用的规划方案来决定由哪一个线程占有互斥量。如果线程的优先权不相等,则通常最高优先权的线程第一个获得释放后互斥量的占有权。如果所有的线程优先权相等,则规划器可能使用一种轮询或FIFO途径来让线程获得互斥量的占有权。
释放或取消锁定互斥量不会释放与互斥量关联的内存,只是放弃了对它的占有权。如果要释放与互斥量关联的内存,必须销毁或关闭互斥量。但是,如果互斥量仍然被占有,或者存在阻塞的线程还等待着该互斥量的释放,就不能销毁或关闭这个互斥量。
OS中的线程支持一种线程间的广播机制,它允许一个线程广播给另一个线程,告诉它某种事件已经发生。这种机制使用的是所谓的条件变量或事件互斥量。当一个线程锁定了一个事件互斥量,它将阻塞,直到它接到了一条广播,告知它可以继续为止。
4. 避免竞争条件
a. 不要使用可能同时位于临界区内的两个进程;
b. 不要依赖对CPU的速度作出的任何假设;
c. 不要让临界区外部停止的进程阻塞其它进程;
d. 不要让进程等待做任意长的时间进入其临界区。
5. 死锁必需的条件
a. 进程声明排它性控制它们需求的资源;
b. 进程在等待其它资源的释放时占有资源;
c. 资源不能强行从进程中删除;
d. 存在一个循环等待条件。
6. 远离死锁
也许用于避免死锁的最简单技术是应用定时互斥量、事件变量、同步变量及信号量。当使用这些机制时,死锁不能发生,因为线程或进程在继续之前只阻塞或等待有限的时间段。
网友评论