深入Phtread(二):线程的同步-Mutex

来源:互联网 发布:nginx配置php环境 编辑:程序博客网 时间:2024/05/22 12:39
并行的世界,没有同步,就失去了秩序,就会乱作一团!试想,交通没有红绿灯,生产线产品装配没有一定的顺序... 结果是显而易见的。多个线程也需要同步,否则程序运行起来结果不可预测,这是我们最不能容忍的。交通的同步机制就是红绿灯,Pthread提供了互斥量(mutex)和条件变量(Condition Variables)两种机制去同步线程。
  1. 不变量,临界区和判定条件
  2. 互斥量(Mutex)
  3. 创建和销毁互斥量
  4. 锁定和解锁
  5. 调整mutex大小
  6. 使用多个mutex
  7. 锁定链

不变量,临界区和判定条件

    不变量(Invariant):程序所做的一些假设,特别是指变量之间的关系。如:一个queue,有头节点,和其它数据节点,这些元素之间的连接关系就是不变量。当程序里面不变量遭受破坏时,后果往往是很严重的,轻则数据出错,重则程序直接崩溃。

    临界区(Critical Section):处理共享数据的一段代码。
    
    判定条件(Predicates):描述不变量状态的逻辑表达式。

互斥量(Mutex)

    一般,多个线程之间都会共享一些数据,当多个线程同时访问操作这些共享数据时。问题出来了,一个线程正在修改数据时,另外一个可能也去操作这些数据,结果就会变得不一致了。如(gv=0是共享的数据):
   
     线程A:a = gv; gv = a + 10; 
     线程B: b = gv; gv = a + 100;
    可能发生A执行完a=gv(0)时,B开始执行b=gv(0); gv=a+100,此时gv=100,然后a执行gv=a+10,最后gv=10。并不是我们要的结果,我们的想法是两个线程并发的给gv加上一个值,期望结果110。^_^ 若这是你银行卡的余额,若没有同步,那就惨了(你往卡里打钱,你有个朋友也同时往你卡里汇钱,很有可能余额只仅加上一方打的)。

    互斥量就是为了解决这种问题而设计的,它是Dijkstra信号量的一种特殊形式。它使得线程可以互斥地访问共享数据。如:

 


    
    上图展示了三个线程共享一个互斥量,位于矩形中心线下方的线程锁定了该互斥量;位于中心线上方且在矩形范围内的线程等待该互斥量被解锁,出于阻塞状态,在矩形外面的线程正常运行。刚开始,mutex是解锁的,线程1成功将其锁定,据为己有,因为并没有其它线程拥有它。然后,线程2尝试去锁定,发现被线程1占用,所以阻塞于此,等到线程1解锁了该mutex,线程2立马将mutex锁定。过了会,线程3尝试去锁定mutex,由于mutex被锁定,所以阻塞于此。线程1调用pthread_mutex_trylock尝试去锁定个mutex,发现该mutex被锁定,自己返回继续执行,并没有阻塞。继续线程2解锁,线程3锁定成功,最后线程3完成任务解锁mutex。

创建和销毁互斥量

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

不要尝试去使用复制的的mutex,结果未定义。

静态创建,当mutex以extern或者static存储时,可以用PTHREAD_MUTEX_INITIALIZER初始化,此时该mutex使用默认属性。
[cpp] view plaincopy
  1. #include "error.h"  
  2. #include <pthread.h>  
  3.   
  4. typedef struct my_struct_tag  
  5. {  
  6.     pthread_mutex_t mutex;  
  7.     int value;  
  8. } my_struct_t;  
  9.   
  10. my_struct_t data = { PTHREAD_MUTEX_INITIALIZER, 0};  
  11.   
  12. int main()  
  13. {  
  14.     return 0;  
  15. }  
    
动态创建,往往使用mutex时,都会将它和共享数据绑在一起,此时就需要pthread_mutex_init去动态初始化了,记得用完后pthread_mutex_destroy。
[cpp] view plaincopy
  1. #include "error.h"  
  2. #include <pthread.h>  
  3.   
  4. typedef struct my_struct_tag  
  5. {  
  6.     pthread_mutex_t mutex;  
  7.     int value;  
  8. } my_struct_t;  
  9.   
  10. int main()  
  11. {  
  12.     my_struct_t* data;  
  13.     int status;  
  14.   
  15.     data = (my_struct_t*)malloc(sizeof(my_struct_t));  
  16.     status = pthread_mutex_init(&data->mutex, NULL);  
  17.     if(status != 0)  
  18.         ERROR_ABORT(status, "pthread_mutex_init");  
  19.   
  20.     pthread_mutex_destroy(&data->mutex);  
  21.     free(data);  
  22.   
  23.     return 0;  
  24. }  
 

锁定和解锁

原则见上面。
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
[cpp] view plaincopy
  1. #include <pthread.h>  
  2. #include <sys/types.h>  
  3. #include "error.h"  
  4. #include <errno.h>  
  5.   
  6. #define SPIN 10000000  
  7.   
  8. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  9. long counter;  
  10. time_t end_time;  
  11.   
  12. void* counter_thread(void* arg)  
  13. {  
  14.     int status;  
  15.     int spin;  
  16.   
  17.     while(time(NULL) < end_time)  
  18.     {  
  19.         status = pthread_mutex_lock(&mutex);  
  20.         if(status != 0)  
  21.             ERROR_ABORT(status, "Lock mutex");  
  22.   
  23.         for(spin = 0; spin < SPIN; spin++)  
  24.             counter++;  
  25.   
  26.         status = pthread_mutex_unlock(&mutex);  
  27.         if(status != 0)  
  28.             ERROR_ABORT(status, "Unlock mutex");  
  29.         sleep(1);  
  30.     }  
  31.   
  32.     printf("Coutner is %#lx/n", counter);  
  33.   
  34.     return NULL;  
  35. }  
  36.   
  37. void* monitor_thread(void* arg)  
  38. {  
  39.     int status;  
  40.     int misses = 0;  
  41.   
  42.     while(time(NULL) < end_time)  
  43.     {  
  44.         sleep(3);  
  45.   
  46.         status = pthread_mutex_trylock(&mutex);  
  47.         if(status != EBUSY)  
  48.         {  
  49.             if(status != 0)  
  50.                 ERROR_ABORT(status, "Trylock mutex");  
  51.               
  52.             printf("Counter is %ld/n", counter/SPIN);  
  53.             status = pthread_mutex_unlock(&mutex);  
  54.             if(status != 0)  
  55.                 ERROR_ABORT(status, "Unlock mutex");  
  56.         }else  
  57.             misses++;  
  58.     }  
  59.     printf("Monitro thread missed update %d times./n", misses);  
  60.     return NULL;  
  61. }  
  62.   
  63. int main()  
  64. {  
  65.     int status;  
  66.     pthread_t pid_counter;  
  67.     pthread_t pid_monitor;  
  68.   
  69.     end_time = time(NULL) + 60;  
  70.   
  71.     status = pthread_create(&pid_counter, NULL, counter_thread, NULL);  
  72.     if(status != 0)  
  73.         ERROR_ABORT(status, "fail to create thread counter");  
  74.   
  75.     status = pthread_create(&pid_monitor, NULL, monitor_thread, NULL);  
  76.     if(status != 0)  
  77.         ERROR_ABORT(status, "fail to create monitor thread");  
  78.   
  79.     status = pthread_join(pid_counter, NULL);  
  80.     if(status != 0 )  
  81.         ERROR_ABORT(status, "fail to join counter thread");  
  82.   
  83.     status = pthread_join(pid_monitor, NULL);  
  84.     if(status != 0)  
  85.         ERROR_ABORT(status, "fail to join monitor thread");  
  86.   
  87.     return 0;  
  88. }  

调整mutex大小

    mutex应该多大?这里的大小是相对的,如mutex锁定到解锁之间的代码只有一行,比起有10行的就小了。 原则是:尽可能大,但不要太大(As big as neccessary, but no bigger)。考虑下面的因素:1> mutex并不是免费的,是有开销的,不要太小了,太小了程序只忙于锁定和解锁了。2> mutex锁定的区域是线性执行的,若太大了,没有发挥出并发的优越性。
3> 自己掂量1和2,根据实际情况定,或者尝试着去做。

使用多个mutex

使用多个mutex一定要注意,防止死锁(deadlock)发生。下面是一个典型死锁:
线程A:pthread_mutex_lock(&mutex_a); pthread_mutex_lock(&mutex_b); ...
线程B:pthread_mutex_lock(&mutex_b); pthread_mutex_lock(&mutex_a); ...

存在这种可能,线程A执行了第一句,锁定了mutex_a;然后线程开始执行第一句锁定mutex_b;然后他们互相等待解锁mutex,A等mutex_b被解锁,B等mutex_a被解锁,不肯让步,出于死锁状态。
[cpp] view plaincopy
  1. #include <pthread.h>  
  2. #include "error.h"  
  3. #include <time.h>  
  4.   
  5. pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;  
  6. pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;  
  7.   
  8. void* thread1(void* arg)  
  9. {  
  10.     while(1)  
  11.     {  
  12.         /*sleep(1);*/  
  13.         pthread_mutex_lock(&mutex_a);  
  14.         pthread_mutex_lock(&mutex_b);  
  15.   
  16.         printf("[%lu]thread 1 is running! /n", time(NULL));  
  17.   
  18.         pthread_mutex_unlock(&mutex_b);  
  19.         pthread_mutex_unlock(&mutex_a);  
  20.     }  
  21.     return NULL;  
  22. }  
  23.   
  24. void* thread2(void* arg)  
  25. {  
  26.     while(1)  
  27.     {  
  28.         /*sleep(1);*/  
  29.   
  30.         pthread_mutex_lock(&mutex_b);  
  31.         pthread_mutex_lock(&mutex_a);  
  32.   
  33.         printf("[%lu]thread 2 is running! /n",time(NULL));  
  34.   
  35.         pthread_mutex_unlock(&mutex_a);  
  36.         pthread_mutex_unlock(&mutex_b);  
  37.   
  38.     }  
  39.     return NULL;  
  40. }  
  41.   
  42. int main()  
  43. {  
  44.     pthread_t tid1, tid2;  
  45.     int status;  
  46.   
  47.     status = pthread_create(&tid1, NULL, thread1, NULL);  
  48.     if(status != 0)  
  49.         ERROR_ABORT(status, "thread 1");  
  50.   
  51.     status = pthread_create(&tid2, NULL, thread2, NULL);  
  52.     if(status !=0)  
  53.         ERROR_ABORT(status, "thread 2");  
  54.   
  55.     status = pthread_join(tid1, NULL);  
  56.     if(status != 0)  
  57.         ERROR_ABORT(status, "join thread1");  
  58.   
  59.     status = pthread_join(tid2, NULL);  
  60.     if(status != 0)  
  61.         ERROR_ABORT(status, "join thread2");  
  62. }  


解决死锁的方法:
固定锁定顺序(Fixed locking hierarchy):锁定mutex的顺序固定。
线程A:pthread_mutex_lock(&mutex_a); pthread_mutex_lock(&mutex_b); ...
线程B:pthread_mutex_lock(&mutex_a); pthread_mutex_lock(&mutex_b); ...

尝试和回退(Try and back off): 锁定第一个后,尝试锁定下一个,若锁定成功,继续尝试下一个,若锁定失败,解锁先去锁定的。

解锁顺序不会引起死锁.
[cpp] view plaincopy
  1. #include <pthread.h>  
  2. #include "error.h"  
  3. #include <errno.h>  
  4.   
  5. #define ITERATIONS 100  
  6.   
  7.   
  8. pthread_mutex_t mutex[3] = {  
  9.     PTHREAD_MUTEX_INITIALIZER,  
  10.     PTHREAD_MUTEX_INITIALIZER,  
  11.     PTHREAD_MUTEX_INITIALIZER  
  12. };  
  13.   
  14. int backoff = 1;  
  15. int yield_flag = 0;  
  16.   
  17. void* lock_forward(void* arg)  
  18. {  
  19.     int i, iterate, backoffs;  
  20.     int status;  
  21.   
  22.     for(iterate = 0; iterate < ITERATIONS; iterate++)  
  23.     {  
  24.         backoffs = 0;  
  25.         for(i = 0; i < 3; i++){  
  26.             if(i == 0)  
  27.             {  
  28.                 status = pthread_mutex_lock(&mutex[i]);  
  29.                 if(status != 0)  
  30.                     ERROR_ABORT(status,"Lock mutex");  
  31.             }else  
  32.             {  
  33.                 if(backoff)  
  34.                     status = pthread_mutex_trylock(&mutex[i]);  
  35.                 else  
  36.                     status = pthread_mutex_lock(&mutex[i]);  
  37.   
  38.                 if(status == EBUSY)  
  39.                 {  
  40.                     backoff++;  
  41.                     printf("forward locker backing off at %d./n", i);  
  42.                     for(; i >= 0; i--)  
  43.                     {  
  44.                         status = pthread_mutex_unlock(&mutex[i]);  
  45.                         if(status != 0)  
  46.                             ERROR_ABORT(status, "Unlock mutex");  
  47.                     }  
  48.                 }else  
  49.                 {  
  50.                     if(status != 0)  
  51.                         ERROR_ABORT(status, "Lock mutex");  
  52.                       
  53.                     printf("forward locker got %d /n", i);  
  54.                 }  
  55.             }  
  56.   
  57.             if(yield_flag){  
  58.                 if(yield_flag > 0)  
  59.                     sched_yield();  
  60.                 else  
  61.                     sleep(1);  
  62.             }  
  63.         }  
  64.   
  65.         printf("lock forward got all locks , %d backoffs/n", backoffs);  
  66.   
  67.         pthread_mutex_unlock(&mutex[2]);  
  68.         pthread_mutex_unlock(&mutex[1]);  
  69.         pthread_mutex_unlock(&mutex[0]);  
  70.         sched_yield();  
  71.     }  
  72.   
  73.     return NULL;  
  74. }  
  75.   
  76. void* lock_backward(void* arg)  
  77. {  
  78.     int i, iterate, backoffs;  
  79.     int status;  
  80.   
  81.     for(iterate = 0; iterate < ITERATIONS; iterate++)  
  82.     {  
  83.         backoffs = 0;  
  84.         for(i = 2; i >= 0; i--){  
  85.             if(i == 2)  
  86.             {  
  87.                 status = pthread_mutex_lock(&mutex[i]);  
  88.                 if(status != 0)  
  89.                     ERROR_ABORT(status,"Lock mutex");  
  90.             }else  
  91.             {  
  92.                 if(backoff)  
  93.                     status = pthread_mutex_trylock(&mutex[i]);  
  94.                 else  
  95.                     status = pthread_mutex_lock(&mutex[i]);  
  96.   
  97.                 if(status == EBUSY)  
  98.                 {  
  99.                     backoff++;  
  100.                     printf("backward locker backing off at %d./n", i);  
  101.                     for(; i < 3; i++)  
  102.                     {  
  103.                         status = pthread_mutex_unlock(&mutex[i]);  
  104.                         if(status != 0)  
  105.                             ERROR_ABORT(status, "Unlock mutex");  
  106.                     }  
  107.                 }else  
  108.                 {  
  109.                     if(status != 0)  
  110.                         ERROR_ABORT(status, "Lock mutex");  
  111.                       
  112.                     printf("backward locker got %d /n", i);  
  113.                 }  
  114.             }  
  115.   
  116.             if(yield_flag){  
  117.                 if(yield_flag > 0)  
  118.                     sched_yield();  
  119.                 else  
  120.                     sleep(1);  
  121.             }  
  122.         }  
  123.   
  124.         printf("lock backward got all locks , %d backoffs/n", backoffs);  
  125.   
  126.         pthread_mutex_unlock(&mutex[0]);  
  127.         pthread_mutex_unlock(&mutex[1]);  
  128.         pthread_mutex_unlock(&mutex[2]);  
  129.         sched_yield();  
  130.     }  
  131.   
  132.   
  133.     return NULL;  
  134. }  
  135.   
  136. int main(int argc, char* argv[])  
  137. {  
  138.     pthread_t forward, backward;  
  139.     int status;  
  140.   
  141.     if(argc > 1)  
  142.         backoff = atoi(argv[1]);  
  143.   
  144.     if(argc > 2)  
  145.         yield_flag = atoi(argv[2]);  
  146.   
  147.     status = pthread_create(&forward, NULL, lock_forward, NULL);  
  148.     if(status != 0)  
  149.         ERROR_ABORT(status, "Create forward");  
  150.   
  151.     status = pthread_create(&backward, NULL, lock_backward, NULL);  
  152.     if(status != 0)  
  153.         ERROR_ABORT(status, "Create backward");  
  154.   
  155.     pthread_exit(NULL);  
  156. }  

锁定链

 
一般用于遍历数据结果(树,链表),一个用于锁定指针,一个锁定数据。
形如:
pthread_mutex_lock(&mutex_a); 
pthread_mutex_lock(&mutex_b); 
...
pthread_mutex_unlock(&mutex_a)
...
pthread_mutex_unlock(&mutex_b)

注意,锁定链往往会出现大量的锁定和解锁操作,有时会得不偿失。
原创粉丝点击