Unix编程 线程局部存储和线程同步
1. 线程局部存储: 不同的线程访问一个同名的变量,这个变量在不同的线程里面对应不同的实例。显然这个变量不能是平凡的,必须用pthread_create_key来创建一个资源表示。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_key_t key;
void print(){printf("%s\n",(char*)pthread_getspecific(key));}
extern "C" void* t(void* arg){
pthread_setspecific(key,arg);
while(1){
print();
sleep(1);
}
return NULL;
}
int main(void){
pthread_t pa,pb;
pthread_key_create(&key,NULL);
pthread_create(&pa,NULL,t,(void*)"Thread a");
pthread_create(&pb,NULL,t,(void*)"Thread b");
pthread_join(pa,NULL);
pthread_join(pb,NULL);
return 0;
}
|
上面的程序里面用到了pthread_join。这是为了防止在一般情况下,如果朱线程退出了,那么所有的子线程也都跟着退出。man上面对于pthread_join的解释是,阻赛主线程的运行,直到join的线程全部运行完毕。
DESCRIPTION
The pthread_join() function suspends processing of the cal-
ling thread until the target thread completes. thread must
be a member of the current process and it cannot be a
detached or daemon thread. See pthread_create(3THR).
在windows环境下由于没有pthread_join这样原语,所以只能用WaitForSingleObject来等待线程退出。由于Windows核心对象都是可以"等待"的handle,所以WaitForSingleObject是一个无敌的通用函数。
#include"stdafx.h"
#include<windows.h>
#include<conio.h>
HANDLE hm;
void* t(void*s){
printf("enter thread\n");
WaitForSingleObject(hm,INFINITE);
printf("end thread\n");
return 0;
}
int main()
{
HANDLE ht=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)t,(LPVOID)("thread 1"),0,0);
hm=CreateMutex(0,TRUE,0);
Sleep(1000);
WaitForSingleObject(ht,1000);
TerminateThread(ht,1);
return 0;
}
|
2. 如果不用线程局部存储,那么为了防止访问冲突,就必须使用线程同步。Posix里面只有信号量没有互斥体(虽然Linux实现了futext),只有pthread线程库里面实现了mutex的语义。下面的程序保证了每次打印的结果,不会互相冲突。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t share=PTHREAD_MUTEX_INITIALIZER;
void print(char* s){printf("%s\n",s);}
extern "C" void* t(void* arg){
while(1){
pthread_mutex_lock(&share);
print((char*)arg);
pthread_mutex_unlock(&share);
sleep(1);
}
return NULL;
}
int main(void){
pthread_t pa,pb;
pthread_create(&pa,NULL,t,(void*)"Thread a");
pthread_create(&pb,NULL,t,(void*)"Thread b");
getchar();
return 0;
}
|
3. 线程等待
下面的例子表示了pthread如何用mutex进行等待。注意假如在子线程tb代码里面,如果只有一句pthread_cond_signal(&cond);会怎么样? 因为pthread_cond_signal函数调用,并不是操作系统当中的一个"原子",也就是说,很可能第一个线程ta调用pthread_cond_wait的过程当中,signal被触发了,然后cond_wait状态才达到。这就相当于丢失了signal。所以,在cond_signal之前必须mutex_lock,保证是ta线程的cond_wait执行完毕了释放了mutex,tb才能得到这个锁,进而执行cond_signal。ta再得到signal,函数pthread_cond_wait返回。这里,mutxt的"加锁解锁"和"得到锁释放锁"是两组不同的概念。pthread_mutex_lock要"得到"锁才能"加锁"。而pthread_cond_wait执行的过程是先得到锁,设置cond_wait状态等待signal到来,然后把继续把mutex设置为加锁状态,直到signal带来才退出和释放锁。得到锁和释放锁是操作系统的原语,是不会冲突的。pthread的信号量是对这原语的包装,不是原子操作。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t share=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond;
extern "C" void* ta(void* arg){
pthread_mutex_lock(&share);
pthread_cond_wait(&cond,&share);
pthread_mutex_unlock(&share);
return 0;
}
extern "C" void* tb(void* arg){
pthread_mutex_lock(&share);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&share);
return 0;
}
int main(void){
pthread_t pa,pb;
pthread_create(&pa,NULL,ta,(void*)"Thread a");
pthread_create(&pb,NULL,tb,(void*)"Thread b");
getchar();
return 0;
}
|
当然,这还不够,如果还有一个线程执行了pthread_cancel(&pa)的话,会发生什么事情? ta中的pthread_cond_wait强制退出了,可是现在它受理还拿着锁呢(锁着的),没有解锁,那么tb就永远无法用pthread_mutex_lock得到锁并加锁了,tb永远都不会被执行。
解决的办法是这样的,类似main函数执行可以注册atexit出口一样,一个线程函数也可以用pthread_cleanup_push来注册一个线程强制退出时,应该再做什么事情。像下面这样。当然,如果线程没有强制退出,记住要删除这个注册信息。把ta的
void myexit(void*){
pthread_mutext_unlock(&share);
}
extern "C" void* ta(void* arg){
pthread_cleanup_push(myexit,NULL);
pthread_mutex_lock(&share);
pthread_cond_wait(&cond,&share);
pthread_mutex_unlock(&share);
pthread_cleanup_pop(0);
return 0;
}
|
pthread_cond_wait可以用条件指示符,来实现程序当中"当某个条件符合的时候,程序才往下进行"这样的,类似Lisp/schema语言的Continuation的功能。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
int x,y;
pthread_mutex_t share=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
extern "C" void* ta(void* arg){
printf("enter ta\n");
pthread_mutex_lock(&share);
while(x<=y){
printf("ta while\n");
pthread_cond_wait(&cond,&share);
}
printf("x=%d,y=%d\n",x,y);
x=4;
y=5;
pthread_mutex_unlock(&share);
return 0;
}
extern "C" void* tb(void* arg){
printf("enter tb\n");
pthread_mutex_lock(&share);
x=9;
y=8;
printf("tb\n");
if(x>y)pthread_cond_broadcast(&cond);
return 0;
}
int main(void){
x=2;
y=3;
pthread_t pa,pb;
pthread_create(&pa,NULL,ta,(void*)"Thread a");
pthread_create(&pb,NULL,tb,(void*)"Thread b");
getchar();
return 0;
}
|
ta的while循环干了什么事情呢? 首先检查条件x<=y,进入pthread_cond_wait,cond_wait对cond加锁,对mutex解锁。然后再次进入while,这个cond是锁定了的,所以第二次pthread_cond_wait等待。然后tb得到锁,线程运行,他设置了x>y,然后pthread_cond_broadcast,这使得ta当中的cond被解锁,同时pthread_cond_wait得到signal执行结束的时候,会再次对mutex加锁(避免别人再得到锁)。再次进入while循环不符合了,退出while。执行ta中下面的程序(此时其他的没有锁的程序不能影响x和y)。
pthread的设计,看起来不如win下面的thread。它的语义太难理解了,就像谜语一样。