因为多个线程是没有运行先后顺序的。系统在调度线程执行的时候,根据内部的调度规则进行调度,让谁执行谁就执行,让谁停止谁就停止。这个规则对于我们程序员或者用户来讲,都是不可见的。在我们看来,里面的调度规则我们毫不知情,也不必知情。我们只需要知道,线程的调度,是无法预测的。所以我们在写多线程程序时,不要期望哪个线程先执行哪个线程后执行。也不要期望线程能一次性全部执行完毕,因为线程随时都可能被系统拉出CPU,放到内存里等待再次调度进CPU。
因此,多个线程在访问临界资源时,访问的开始时间、持续时间、结束时间都是无法预测的。多个线程之间,谁此时在执行也无法知晓。所以,在对临界资源的共享使用时,如果要确保正确使用,我们需要进行一些保护。因为同时对全局变量赋值,因为多个线程先后不确定,你也不知道最终全局变量得到了什么值。进而得知,此时读取出来的值你也无法确定。这样也就让结果会很怪异。读取的值一会是这个,一会是那个。
所以,我们使用锁机制来做一个保护。当一个线程在使用临界资源前,先看看资源有么有被加锁,如果被锁住了,那么就等待资源被解锁后再使用,没有被加锁,就直接使用,并且加锁资源,防止他人使用。提示一下,多进程和多线程差不多,就不单独描述多进程了。
使用的流程如下:
1.资源是否被其他人锁住。
2.如果锁住,等待解锁。
3.如果没有锁住,使用资源,锁住资源。
4.用完解锁释放资源。
这个基本的逻辑相信大家也都知道。但是还是可能出现问题,那就是死锁的问题。死锁发生在你锁住了资源不释放资源就退出了。这样他人无法使用资源,而你也不再释放资源,他人一直死等这个资源的释放。症状就是,CPU使用率为0,线程一直处于等待阻塞状态,时间长之后,甚至会被系统挂起来。
我的代码中出现这个死锁问题,原因在于在函数开头访问共享资源时锁定,然后函数结尾时解锁。后续再修改代码时,在中途直接返回了,而没有解锁。而这个问题只有在中途触发了直接返回的代码时才表现出来,很容易让人蒙圈。
好在我们可以通过CPU使用率、程序执行情况加以判断。我判断的依据就是,线程的CPU使用率为0%,程序不能正常响应请求。如果是死循环,CPU会形成很高的使用率,同时导致不能正常响应请求。
那么死锁如何检测呢?有没有官方或者高大上的检测工具呢?以前听说过有死锁检测工具,不过查了一下之后,发现有点麻烦。但是又要解决死锁检测问题,怎么办呢?
然后突然想到了解决办法,那就是打日志。打日志是一种很常见的调试跟踪手段,可以加以利用,比那些检测工具好用多了,简单明了。思路就是,在请求锁之前,打印一个日志,访问资源时打一个日志,解锁退出之后打一个日志。如果一个地方连续出现三条日志,没有问题。如果只出现第一条,那么这一处就一直处于等待状态。如果一处出现两条,那么就是中场退出,没有解锁。这样也就造成了死锁。
通过日志文件,我们就可以一目了然的看到锁的使用状态,从而可以迅速定位死锁的位置。下面是我实际检测到死锁的日志文件截图:
写这篇经验的目的在于,分享一个简单的检测死锁的方法,让我们知道,不要迷信权威的检测办法。因为我一开始就从权威的检测工具上面去思考检测死锁问题了,然后无果,也懒得继续查,就用日志的方式解决了。