多个线程同时访问共享数据时可能会发生冲突,如两个线程都要把某个全局变量增加1,这个操作在平台上需三条指令完成:
(1)从内存中读变量值到寄存器中;
(2)寄存器的值加1;
(3)将寄存器的值写回内存;
假设线程1执行了(1)、(2),正要执行(3)时,被强制切换出去,线程1保存自己的上下文信息;线程2被切换进来,执行了(1)、(2)、(3),则变量值发生改变,被写回内存,线程2结束后,线程1 根据自己的上下文信息,继续(3),则此时内存中由线程2确定的变量则被线程1确定的变量值所覆盖,造成数据的二义性,因此要对各个线程访问共享数据进行保护。
程序代码:
测试:
这个结果显然不符合预期,两个线程访问共享数据时有了冲突;为了解决这个问题,可引入互斥锁,获得锁的线程可完成“读-修改-写”的基本操作,可释放锁给其他线程,未获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”操作组成一个原子操作,要么执行,要么不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
基本函数如下:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;(相当于用pthread_mutex_init初始化并且attr参数为NULL)
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
加锁或解锁操作:(成功返回0)
int pthread_mutex_lock(pthread_mutex_t *mutex);(阻塞式)
int pthread_mutex_trylock(pthread_mutex_t *mutex);(非阻塞式)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
程序代码:
测试:
通过加互斥锁,很好地解决了多个线程同时访问共享数据时可能会发生冲突的问题,但加上互斥锁,也会带来一些问题,比如常见的死锁问题:
(1)若同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正被自己占用着,该线程又被挂起而没有机会释放锁,就永远处于挂起等待状态,形成死锁;
(2)线程A获得锁1,线程B获得锁2,此时,线程A试图获得锁2,需挂起等待线程B释放锁2,而线程B也试图获得锁2,需挂起等待线程A释放锁1,因此,线程A与线程B永远处于挂起状态了,形成死锁;
死锁定义:若一组进程中的每一个进程都在等待仅由该组进程中的其它进程才能引发的事件,那么该组进程是死锁的。
产生死锁的必要条件:(以下4个条件任一个不成立,死锁就不会发生)
(1)互斥条件:在一段时间内,某资源只能被一个进程占用;
(2)请求和保持条件:进程已拥有了至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占用,此时请求进程被阻塞,但对自己已获得资源保持不放;
(3)不可抢占条件:进程已获得资源在未使用完之前不能被抢占,只能进程使用完之后自己释放;
(4)循环等待条件:进程集合{p0,p1,p2,...}中p0在等待一个被p1占用的资源,p1等待一个被p2占用的资源,...pn等待被p0占用的资源;
如何预防死锁?
(1)破坏“请求和保持”条件
第一种协议:所有进程在开始运行之前,必须一次性地申请其在整个运行过程所需要的全部资源;
第二种协议:它允许一个进程只获得运行初期所需的资源后,便开始运行。进程运行过程中再逐步释放已分配给自己的、且已用完的全部资源,然后再请求新的所需资源;
(2)破坏“不可抢占”条件
当一个已经保持了某些不可被抢占资源的进程,提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请;
(3)破坏“循环等待”条件
对资源进行线性排序,并赋予不同的序号,规定每个进程必须按序号递增的顺序请求资源;
如何避免死锁?
系统处于安全状态时,可避免死锁的发生。
安全状态:指进程能够按照某种进程推进顺序(p0,p1,p2,...)为每个进程pi分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序地完成,此时称(p0,p1,p2,...)为安全序列,若进程中找不到这样一个安全序列,则进程处于不安全状态,进程处于不安全状态,易造成死锁。
可利用银行家算法避免死锁;
银行家算法思想:每一个进程在运行之前,必须申明在运行过程中,可能需要的每种资源类型的最大单元数目,其数目不应超过系统所拥有的资源总量,当进程请求一些资源时,系统必须首先确定是否有足够的资源分配给该进程,若有,再进一步计算在将这些资源分配给进程后,是否会使系统处于不安全状态,若不会,才将资源分配给它,否则让进程等待。