(1)首先来了解线程的同步与互斥:
当多个线程访问同一个进程时的临界区时需要被同步与互斥保护避免产生冲突。比如当两个线程都要把某个全局变量增加1,这个操作在某平台上需要三条指令完成:1,从内存读变量到寄存器。2,寄存器的值加1。3,将寄存器的值写回内存。
来看这个程序:
#include<stdio.h>
2 #include<pthread.h>
3 #define NLOOP 5000
4
5 int count=0;
6 void *thread_count(void *arg)
7 {
8 int i=0;
9 int tmp=0;
10 while(i<NLOOP){
11 printf("count is:%d\n",count++);
12 i++;
13 }
14 }
15 int main()
16 {
17 pthread_t id1,id2;
18 pthread_create(&id1,NULL,thread_count,NULL);
19 pthread_create(&id2,NULL,thread_count,NULL);
20 pthread_join(id1,NULL);
21 pthread_join(id2,NULL);
22 printf("final count is:%d\n",count);
23 return 0;
24
25 }
他的运行结果:
来分析这个程序:首先创建了两个线程,count是一个全局变量,当第一个线程运行完了之后count的值是5000,当运行第二个线程的时候会在第一个线程的基础上,也就是count=5000的基础上继续往上加,所以10000是两个线程count总和。但是当把NLOOP的值变大到50000或者更大的时候就会出现错误,有时候它们的和比100000小,有时候比这个数大,有时候刚好就是100000。出现这个错误就是因为:(1)count不是原子的。(2)是在线程切换的时候出现了累加错误。要避免这样的错误就是在两个线程进行切换的时候,当线程从内核态向用户态切换的时候需要检查,还有就是要保证临界资源的原子性,所以我们引入互斥锁。
(2)互斥锁:
就像上面例子一样,多个线程访问一个程序的时候经常会出现访问冲突的问题,所以我们引入互斥锁来解决(Mutex,Mutual Exclusive Lock),比如现在线程A,B,C,D在申请访问一个程序,但必须他们必须先有锁,所以他们先申请锁,如果A获得锁的线程可以完成“读--修改--写”的操作,然后释放锁给其他线程,而那些没有获得锁的线程(B,C,D)只能等待而不能访问共享数据,这样“读--修改--写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中期被打死,也不会在其他处理器上并行做这个操作。
先来看Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:
返回值:成功返回0,错误返回错误码。
Mutex的加锁与解锁:
返回值:成功返回0,失败返回错误码。
一个线程可以调用pthread_mutex_lock来获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,这个函数会失败返回EBUSY,而不会使线程挂起等待。
现在我们来解决上面程序中提到的问题:
#include<stdio.h>
2 #include<pthread.h>
3 #define NLOOP 5000
4
5 int count=0;
6 pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
7 void *thread_count(void *arg)
8 {
9 int i=0;
10 int tmp=0;
11 while(i<NLOOP){
12 pthread_mutex_lock(&lock);
13 printf("count is:%d\n",count++);
14 pthread_mutex_unlock(&lock);
15 i++;
16 }
17 }
18 int main()
19 {
20 pthread_t id1,id2;
21 pthread_create(&id1,NULL,thread_count,NULL);
22 pthread_create(&id2,NULL,thread_count,NULL);
23 pthread_join(id1,NULL);
24 pthread_join(id2,NULL);
25 printf("final count is:%d\n",count);
26 return 0;
27
28 }
结果如图所示:
在互斥锁的操作中,大多数体系结构都提供了swap或者exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。
(3)死锁:
死锁有两种有两种情形:一:如果同一个线程先后两次调用lock,在第二次调用时由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着,该线程又被挂起而没有机会释放锁,所以它就永远停在等待状态,这叫做死锁。二:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也要调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程AB都永远处于挂起状态了。
解决方法:在程序中我们一般使用pthread_mutex_trylock来代替pthread_mutex_trylock避免死锁,或者使用一种稍微复杂一点的方法:如果线程需要多个锁时都按照相同的先后顺序获得锁。
综上所述,形成死锁的原因是:1,系统资源不足。2,线程推进的顺序不当。3,资源分配不当。