线程的互斥与死锁问题
线程的互斥
图中g_count是全局变量,从0开始。线程1和线程2对g_count进行++(5000次)操作。
如果线程1刚拿到g_count,被切断,然后线程2进行对g_count进行++,然后被g_count++到5000,线程2结束,此时将g_count变为了5000,再进行线程1操作,此时线程1拿到了最开始的0,就从0开始++,结果将g_count覆盖。
正常结果应该加到10000,但是被覆盖了只能到5000。
出现这些的原因:
1、进程内部全局变量是线程共享的
2、全局变量属于临界资源
3、不是原子操作,导致不一致性
4、解决:就是将非原子改为原子即互斥。
写一个代码模拟上面的过程
这个代码跑出来的结果正确,但是不能说明这样是没有错误的。在计算机中,加5000次可以很快结束,将5000换为大点的数就会出现错误。但是代码会跑很久。一般进行从用户态到内核态的切换和内核态到用户态的切换会导致线程的切换。
那么这里就提到用户态和内核态。那什么是用户态?什么是内核态?用户态就相当于用户,就是只能运行自己的代码。而内核态就是权限比较高的,可以完成好多事。在调用系统调用的接口时,会出现用户态与内核态的转换。调用系统调用时,系统调用接口是操作系统提供的,它只有内核态这样权限高的才能访问。在调用系统接口时,操作系统就会将用户态切为内核态,完成调用后再将内核态切为用户态。
于是代码可以改为:
前边提到说要加系统调用才能进行线程交换,为什么printf就可以了呢?
printf分装了系统调用,底层调用了write。
结果:(就是预期效果,两次累加没到10000)
互斥加锁
对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁,获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成⼀一个原子操作,要么 都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
在Linux中 加锁和释放锁的函数是:
其中前两个是加锁,第一个是阻塞式等待,第二个是非阻塞式等待。它两都是成功返回0错误返回错误码。pthread_mutex_lock如果没有申请到锁就挂起等待,pthread_mutex_trylock没申请到锁就返回EBUSY。
第三个是释放锁。
锁以变量进行定义必须要初始化
下图为初始化和销毁
静态变量用宏进行初始化PTHREAD_MUTEX_INITIALIZER就是第三个
第一个是初始化函数,第二个是销毁锁
加锁后代码变为:
结果就正确,累加了10000次:(如下)
死锁的产生
死锁是怎么产生的?
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
那么什么叫死锁?
是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
死锁产生的四个必要条件?
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解决
1、预防死锁。
这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
2、 避免死锁。
该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。
3、检测死锁。
这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中将已发生的死锁清除掉。
4、解除死锁。
这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
上一篇: 什么情况下Java程序会产生死锁?如何定位、修复?
下一篇: 【opencv】棋盘格角点检测