线程的同步与互斥

多线程往往会引起很多问题,比如下面所示代码:

  1 #include<stdio.h>
  2 #include<pthread.h>
  3 int g_val=0;
  4 void *test(void *arg) 
  5 {
  6     int count=5000;
  7     int tmp=0;
  8     while(count-->0)
  9     {
 10         tmp=g_val;
 11         printf("%sg_val=%d\n",arg,g_val);
 12         g_val=tmp+1;
 13     }   
 14 }   
 15 int main()
 16 {
 17     pthread_t thread1=0;
 18     pthread_t thread2=0;
 19     pthread_create(&thread1,NULL,test,"thread1");
 20     pthread_create(&thread2,NULL,test,"thread2");
 21     pthread_join(thread1,NULL);
 22     pthread_join(thread1,NULL);
 23     printf("the g_val=:%d\n",g_val);
 24 }

结果如下

wKiom1cTPknjOwEAAABllcwBqDc736.png

结果并不是应该的10000,为什么呢?

因为在g_val++;的这条语句中cpu实际上对应3条指令,读取,+1,写回,在这个过程中会出现这样的情况pthread1先拿到了g_val然后将它放在了寄存器中加一,这时候转换到了pthread2上它也将g_val拿到了寄存器中,然后+1,返回。导致多次加法加了一次。

互斥量(mutex)

为了满足线程间的互斥要求,引入了mutex它就像是一把锁,在访问一个敏感的数据时,加上这把锁其它要访问这个数据的线程就会被阻挡在外,直到它解开了锁才可以访问。

       #include <pthread.h>

       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

新建一把锁我们有两个方式:

mutex_init可以在main函数中动态创建一把锁,第一个参数为输出型参数,第二个可以设置为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);

这三个函数是用来加锁和解锁的,其中的trylock是lock的非阻塞版本,如果mutex参数所指定的互斥锁已经被锁定 的话,调用pthread_mutex_trylock函数不会阻塞当前线程,而是立即返回一个值来描述互 斥锁的状况。

下面是上述例子加锁后的情况:

  1 #include<stdio.h>
  2 #include<pthread.h>
  3 int g_val=0;
  4 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  5 void *test(void *arg)
  6 {
  7     int count=5000;
  8     int tmp=0;
  9     while(count-->0)
 10     {
 11         pthread_mutex_lock(&mutex);
 12         tmp=g_val;
 13         printf("%sg_val=%d\n",arg,g_val);
 14         g_val=tmp+1;
 15         pthread_mutex_unlock(&mutex);
 16     }
 17 }
 18 int main()
 19 {
 20     pthread_t thread1=0;
 21     pthread_t thread2=0;
 22     pthread_create(&thread1,NULL,test,"thread1");
 23     pthread_create(&thread2,NULL,test,"thread2");
 24     pthread_join(thread1,NULL);
 25     pthread_join(thread1,NULL);
 26     sleep(1);
 27     printf("the g_val=:%d\n",g_val);
 28 }

wKiom1cTP4LhrKbsAABe1EVXL_k891.png

这时便不会产生混乱的情况了

那么mutex内部到底是如何实现的呢?

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄

存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。


思考:如果test函数改成这样呢

  6 {
  7     int count=5000;
  8     int tmp=0;
  9     while(count-->0)
 10     {
 11         pthread_mutex_lock(&mutex);      //又不小心的加了一把锁
 12         tmp=g_val;
 13         pthread_mutex_lock(&mutex);
 14         printf("%sg_val=%d\n",arg,g_val);
 15         g_val=tmp+1;
 16         pthread_mutex_unlock(&mutex);
 17     }
 18 }

这就会导致死锁的发生。

死锁它是指多个进程或线程在运行的时候因争夺资源而造成的一种僵局状态。

死锁产生的原因

1>两个进程因争夺非剥夺性资源或临时性资源,例如有P1和P2两个进程,P1占用了摄像头P2占用了打印机,这时候P2继续要求用摄像头,而P1要求用打印机,这时候便形成了僵局。

2>进程在请求和释放资源的时候的顺序不当也会导致死锁。

产生死锁的必要条件:

1>互斥条件:指金城对所分配的资源进行独占使用,如果这时候再有其他进程要求此资源则会阻塞到该资源释放。

2>请求保持条件:指进程此时已经拥有了至少一个资源而又要求其他资源但又获取不到该资源,他又对自己保持的资源不放。

3>不剥夺条件指进程已获得的资源,在未使用完之前,不能被剥夺,只能在自己使用完之后自己释放。

4>环路等待条件:在发生死锁的时候必然存在一个进程和资源的环形链。


在上述metux程序中还会出现另一种现象:

线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。

解决方法:

在写程序时应该避免同时获得多个锁的情况,如果非要这样,可以按照顺序加锁,比如A程序加锁1,2,3,B程序也是锁1,2,3的顺序,这时候就不会发生冲突导致死锁的情况了。