下面是我遇到的一个多线程程序中诡异事件的解决过程:主线程main启动 thread1 和 thread2,thread2保持阻塞,接到thread1发出的信号后工作,完毕后发出终了信号通知thread1,再次阻塞...直到thread1发出结束信号为止。代码如下:(信号用全局变量实现)thread.h#include <pthread.h> #include <stdio.h> #ifndef ARG extern pthread_mutex_t mutex; extern pthread_cond_t cond; extern int task_ready; extern int main_task; //extern volatile int task_ready; //extern volatile int main_task; enum Task { IDLE = 0, SMILE, MOAN, DIE }; #else pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int task_ready; int main_task; //volatile int task_ready; //volatile int main_task; #endif void func_1(); void func_2();
thread.c
#define ARG #include "thread.h" int main() { pthread_t thread1, thread2; int iret1, iret2; iret1 = pthread_create( &thread1, NULL, (void*)func_1, NULL); iret2 = pthread_create( &thread2, NULL, (void*)func_2, NULL); pthread_join( thread1, NULL); pthread_join( thread2, NULL); printf("Thread 1 returns: %d/n",iret1); printf("Thread 2 returns: %d/n",iret2); return (0); } thread1.c
#include "thread.h" void send_task(int task) { printf("thread1---send_task: %d /n",task); while(!task_ready) { //printf("thread1---waiting for task to be ready/n"); } pthread_mutex_lock(&mutex); task_ready = 0; main_task = task; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); while(main_task) { //printf("thread1---waiting for task to be done/n"); } } void func_1() { printf("thread1---thread1 start /n"); send_task(SMILE); send_task(MOAN); send_task(DIE); printf("thread1---thread1 over /n"); }
thread2.c
#include "thread.h" void func_2() { int over = 0; printf("thread2---thread2 start /n"); while(!over) { pthread_mutex_lock(&mutex); task_ready = 1; printf("thread2---hanging and waiting.../n"); pthread_cond_wait(&cond,&mutex); printf("thread2---got task: %d /n",main_task); switch(main_task) { case SMILE: printf("thread2---Haahahhahahah!~/n"); break; case MOAN: printf("thread2---OhhAhhhOhhhhh!~/n"); break; case DIE: default: printf("thread2---I'm dying...!~/n"); over = 1; break; } sleep(3); main_task = IDLE; pthread_mutex_unlock(&mutex); } printf("thread2---thread2 over /n"); }
编译:#gcc -o Thread thread.c thread1.c thread2.c -pthread 运行,正常结果是(指的是不会卡住,某些步骤会有先后变化)#./Thread thread1---thread1 start thread1---send_task: 1 thread2---thread2 start thread2---hanging and waiting...thread2---got task: 1 thread2---Haahahhahahah!~thread2---hanging and waiting...thread1---send_task: 2 thread2---got task: 2 thread2---OhhAhhhOhhhhh!~thread2---hanging and waiting...thread1---send_task: 3 thread2---got task: 3 thread2---I'm dying...!~thread2---thread2 over thread1---thread1 over Thread 1 returns: 0Thread 2 returns: 0 但是结果多变,常常卡在 while(!task_ready) { //printf("thread1---waiting for task to be ready/n"); } //或者: while(main_task) { //printf("thread1---waiting for task to be done/n"); }
(thread2 运行越慢,越易重现,所以我故意在thread2.c 内加入 sleep(3);) 程序逻辑没问题,不存在死锁,为什么会卡在上面两步?明明 task_ready 已由 thread2 修改过了,thread1的这个语句为什么”不知道“?为什么一个线程察觉不到别的线程对公共全局变量的修改?这时候,忽然想到了一个从未用过的很奇特的限定符(qualifier)volatile:通知编译器,即使当前的程序(进程/线程)未修改它所标识的变量,此变量值也会改变。(可能其他程序/线程/硬件修改这个变量)。为什么需要通知?先看看一句代码的汇编实现:(是用ARM实现的,我的机器是AMD64的,这两句没产生优化)#... #int a=0;while(a); #... .LFB0: .L2: b .L2 #编译器认为永远 a = 0;故直接优化成死循环。 #另一个 #volatile int a=0;while(a); .LFB0: ldr r2, .L6 .pad #8 sub sp, sp, #8 .LCFI0: mov r3, #0 str r3, [sp, #4] .L2: ldr r3, [sp, #4] cmp r3, r2 ble .L2 add sp, sp, #8 @ sp needed for prologue bx lr
因为,有时,编译器会对频繁使用的变量或者它认为“不会改变”的变量进行优化,对变量取值时不是从每次从内存-->寄存器;而是直接一次取出保存在寄存器内,以后直接从寄存器取。问题就在于此:"似乎"不会改变的 task_ready和 main_task会被thread2所修改,此处的“优化”就是问题所在,解决方式就是相关变量加上volatile即可。