线程死锁

xiaoxiao2021-02-28  138

一:mutex(互斥量) 多个线程同时访问共享数据时可能会冲突,比如两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成: 1:从内存读取变量值到寄存器 2:寄存器的值加1 3:将寄存器的值写回内存 如:我们创建两个线程,各自把全局count增加5000,我们希望最后的count应该是10000次,可是每次运行的结果都不一样.

如: #include<stdio.h> #include<unistd.h> #include<pthread.h> #include<sys/types.h> static int count =0; void*pthread_run(void*arg) { int tmp = 0; int i = 0; while(i<5000) { ++i; tmp = count; printf("thread is %u,count is %d\n",pthread_self(),count); count =tmp+1; } return NULL; } int main() { pthread_t tid1; pthread_t tid2; pthread_create(&tid1,NULL,pthread_run,NULL); pthread_create(&tid2,NULL,pthread_run,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); printf("count is %d\n",count); return 0; }

我们看下结果: 第一次: 第二次: 那么是如何造成这个问题的呢? 这就是常说的在多线程的程序中,访问冲突的问题.解决的办法是引入互斥锁(Mutex,Mutex Exclusive Lock),获得锁的线程可以完成”读-修改-写”操作,然后释放锁给其他线程,没有获得锁的线程只能等待而不能访问共享数据,这样”读-修改-写”三部的操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其他处理器上并行做这个操作.

Mutex用pthead_mutex_t类型的变量表示,可以这样表示初始化和销毁

返回值:成功返回0,失败返回错误号 我们可以看到pthead_mutex_init函数对Mutex做初始化,设定Mutex的属性,如果attr为NULL则表示缺省属性.当然我们也可以pthead_mutex_destory销毁,如果Mutes变量是静态分配的(全局变量或者static变量),也可以用宏定义PTHREAD_MUTES_INITIALIZER来初始化,相当于用pthread_mutex_init初始化,并且attr参数为NULL, 二:Mutex的加锁和解锁: 三个函数: pthread_mutex_lock:以原子操作一个互斥锁加锁,如果已经目标已经被加锁,则pthread_mutex_lock被阻塞,直到互斥锁占有者给它解锁. pthread_mutex_trylock:和pthread_mutex_lock类似,不过它始终立即返回,而不论被操作的互斥锁是否加锁,当目标被倍加锁时,pthread_mutex_teylock进行加锁操作,否则将返回EBUSY错误码 pthread_mutex_unlock:以原子的操作给一个互斥锁进行解锁. 返回值:成功返回0,失败返回错误号; 返回值:成功返回0,失败返回错误号; 通俗的说一个线程可以调用pthread_mutex_lock获得Mutex(互斥量),如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex(互斥量),则当前进程需要挂起等待,直到另外一个线程调用那个pthread_mutex_unlock释放Mutex,当前进程被唤醒,才能获得该Mutex并继续执 如果一个线程即想获得锁,又不想调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会是线程挂起等待.

//验证 #include<stdio.h> #include<unistd.h> #include<pthread.h> #include<sys/types.h> static int count =0; pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;//表明初始化 void*pthread_run(void*arg) { int tmp = 0; int i = 0; while(i<5000) { ++i; pthread_mutex_lock(&mutex_lock);//加锁 tmp = count; printf("thread is %u,count is %d\n",pthread_self(),count); count =tmp+1; pthread_mutex_unlock(&mutex_lock);//释放锁 } return NULL; } int main() { pthread_t tid1; pthread_t tid2; pthread_create(&tid1,NULL,pthread_run,NULL); pthread_create(&tid2,NULL,pthread_run,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); printf("count is %d\n",count); return 0; }

通过上面的实例我们知道互斥量本质上来说就是一把锁,提供对共享资源的保护访问. 看到这个结果可能会感到奇怪,Mutex的两个基本操作lock和unlock是如何实现的?前面我们知道多个线程访问时会出现冲突的问题,当引入互斥锁后,当一个线程加锁后其他线程只能挂起等待,只与等这个线程获得解锁后,唤醒等待的其他线程.这样我们就不难理解第二次这两个全局的Count进行增加5000次,结果是10000了. 我们可以用伪代码来表示下加锁和解锁:

lock: if(mutex > 0 ) { mutex =0; return 0 ; } else: lock;挂起等待 unlock: mutex = 1; 唤醒等待Mutex的线程; return 0;

当然了unlock操作中唤醒等待线程可以有不同的实现,可以唤醒一个等待线程,也可以唤醒所有等待该Mutex的线程,然后让这些被唤醒的这些线程去竞争获得这个Mutex,竞争失败的线程继续挂起等待 “挂起等待”和”唤醒等待线程” 每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先把自己假如等待队列中,然后置线程状态为睡眠.然后调度器函数切换到别的线程,只需从等待队列中取一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行是就有可能切换到被唤醒的线程, 三:死锁 死锁现象: 如果同一个线程先后矿磁调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着,该线程又被挂起而美欧机会释放锁,因此就永远挂起等待状态了,这叫死锁. 另外一种典型的死锁: 线程A获得锁1,线程B获得锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和线程B都永远处于挂起状态了. 产生死锁的四个必要条件: 1:互斥条件:一个资源每次只能被一个进程(线程)使用; 2:占有和等待条件:已经分配给某个资源的线程可以请求新的资源 3:不可抢占条件:已经分配给一个进程的资源不能强行地被抢占,它只能占有它的进程显示的释放

4:循环等待条件:系统中一定有两个或者两个以上的进程组成一条环路,该环路中的每一个进程都在等待着下一个进程所占有的资源。 对线程之间形成一种头尾相接的循环等待资源关系 预防死锁: 预防死锁的根本办法就是要使死锁产生的4个必要条件之一不存在. 1:破坏互斥条件: 破坏互斥条件即允许多个进程访问资源,由于多数资源的必须互斥访问,因此通过破坏互斥条件多数情况下是行不通的 2:破坏占有和等待条件 采用资源静态分配法,就是线程运行前,一次性的分配它运行所需要的全部资源 若系统有足够的资源分配给某一个线程,则一次性第将其所需要的资源分配给该线程,这样运行期间就不会提出任何资源请求,从而是等待条件不成立.如果分配时有一种资源要求不能满足,则线程需要其他资源也先不分配给线程,从而避免进程在等待期间占用任何资源,破坏占用条件,从而避免死锁的发生. 3:破坏不剥夺条件 使一个已保持了某些资源的进程,由于新的资源要求目前得不到满足,它必须先暂时释放以保持的所有资源,然后去等待,以后再向系统申请,这样也能防止死锁 4:破坏循环等待条件: 申请新锁的时候,先把自己的锁释放,在申请新锁,就不会陷入循环等待中没这样就可以解除死锁. 解除死锁: 死锁的解除的实值让释放资源的线程能够继续运行 1:剥夺资源: 使用挂起机制挂起一些线程,剥夺它们占有的资源给死锁线程,以解除死锁,等到条件满足后,再唤醒被挂起的线程 2:撤销进程 可以直接撤销代价最小的进程达到解除死锁的目的,直到有足够的资源可用解除死锁.

转载请注明原文地址: https://www.6miu.com/read-18817.html

最新回复(0)