嵌入式linux中文站给咱们介绍三种Linux中的常用多线程同步办法:互斥量,条件变量,信号量。
1 互斥锁
互斥锁用来确保一段时刻内只要一个线程在履行一段代码。必要性清楚明晰:假定各个线程向同一个文件次第写入数据,最终得到的成果一定是灾难性的。
先看下面一段代码。这是一个读/写程序,它们共用一个缓冲区,而且假定一个缓冲区只能保存一条信息。即缓冲区只要两个状况:有信息或没有信息。
void reader_function ( void );
void writer_funcTIon ( void );
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct TImespec delay;
void main ( void ){
pthread_t reader;
/* 界说延迟时刻*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默许特点初始化一个互斥锁目标*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_funcTIon), NULL);
writer_funcTIon( );
}
void writer_function (void){
while(1){
/* 确定互斥锁*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 翻开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
这儿声明晰互斥锁变量mutex,结构pthread_mutex_t为不揭露的数据类型,其间包括一个体系分配的特点目标。函数pthread_mutex_init用来生成一个互斥锁。NULL参数标明运用默许特点。假如需求声明特定特点的互斥锁,须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁特点。前一个函数设置特点pshared,它有两个取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进程的不同线程。在上面的比如中,运用的是默许特点PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们别离界说了不同的上锁、解锁机制,一般情况下,选用最终一个默许特点。
pthread_mutex_lock声明开端用互斥锁上锁,尔后的代码直至调用pthread_mutex_unlock中止,均被上锁,即同一时刻只能被一个线程调用履行。当一个线程履行到pthread_mutex_lock处时,假如该锁此刻被另一个线程运用,那此线程被堵塞,即程序将等候到另一个线程开释此互斥锁。在上面的比如中,运用了pthread_delay_np函数,让线程睡觉一段时刻,便是为了避免一个线程一直占据此函数。
在运用互斥锁的进程中很有可能会呈现死锁:两个线程企图一同占用两个资源,并按不同的次第确定相应的互斥锁,例如两个线程都需求确定互斥锁1和互斥锁2,a线程先确定互斥锁1,b线程先确定互斥锁2,这时就呈现了死锁。此刻能够运用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非堵塞版别,当它发现死锁不可避免时,它会回来相应的信息,程序员能够针对死锁做出相应的处理。别的不同的互斥锁类型对死锁的处理不相同,但最主要的仍是要程序员自己在程序设计留意这一点。
2 条件变量
前一节中咱们叙述了怎么运用互斥锁来完成线程间数据的同享和通讯,互斥锁一个显着的缺陷是它只要两种状况:确定和非确定。而条件变量经过答应线程堵塞和等候另一个线程发送信号的办法弥补了互斥锁的缺乏,它常和互斥锁一同运用。运用时,条件变量被用来堵塞一个线程,当条件不满意时,线程往往解开相应的互斥锁并等候条件发生变化。一旦其它的某个线程改动了条件变量,它将告诉相应的条件变量唤醒一个或多个正被此条件变量堵塞的线程。这些线程将从头确定互斥锁并从头测验条件是否满意。一般说来,条件变量被用来进行线程间的同步。
条件变量的结构为pthread_cond_t,函数pthread_cond_init()被用来初始化一个条件变量。它的原型为:
extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));
其间cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的特点结构,和互斥锁相同能够用它来设置条件变量是进程内可用仍是进程间可用,默许值是PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程运用。留意初始化条件变量只要未被运用时才干从头初始化或被开释。开释一个条件变量的函数为pthread_cond_ destroy(pthread_cond_t cond)。
函数pthread_cond_wait()使线程堵塞在一个条件变量上。它的函数原型为:
extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
pthread_mutex_t *__mutex));
线程解开mutex指向的锁并被条件变量cond堵塞。线程能够被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒,可是要留意的是,条件变量仅仅起堵塞和唤醒线程的效果,详细的判别条件还需用户给出,例如一个变量是否为0等等,这一点从后边的比如中能够看到。线程被唤醒后,它将从头查看判别条件是否满意,假如还不满意,一般说来线程应该仍堵塞在这儿,被等候被下一次唤醒。这个进程一般用while句子完成。
另一个用来堵塞线程的函数是pthread_cond_timedwait(),它的原型为:
extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
pthread_mutex_t *__mutex, __const struct timespec *__abstime));
它比函数pthread_cond_wait()多了一个时刻参数,阅历abstime段时刻后,即便条件变量不满意,堵塞也被免除。
函数pthread_cond_signal()的原型为:
extern int pthread_cond_signal __P ((pthread_cond_t *__cond));
它用来开释被堵塞在条件变量cond上的一个线程。多个线程堵塞在此条件变量上时,哪一个线程被唤醒是由线程的调度战略所决议的。要留意的是,有必要用维护条件变量的互斥锁来维护这个函数,不然条件满意信号有可能在测验条件和调用pthread_cond_wait函数之间被宣布,然后形成无限制的等候。下面是运用函数pthread_cond_wait()和函数pthread_cond_signal()的一个简略的比如。
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count () {
pthread_mutex_lock (&count_lock);
while(count==0)
pthread_cond_wait( &count_nonzero, &count_lock);
count=count -1;
pthread_mutex_unlock (&count_lock);
}
increment_count(){
pthread_mutex_lock(&count_lock);
if(count==0)
pthread_cond_signal(&count_nonzero);
count=count+1;
pthread_mutex_unlock(&count_lock);
}
count值为0时,decrement函数在pthread_cond_wait处被堵塞,并翻开互斥锁count_lock。此刻,当调用到函数increment_count时,pthread_cond_signal()函数改动条件变量,奉告decrement_count()中止堵塞。
函数pthread_cond_broadcast(pthread_cond_t *cond)用来唤醒一切被堵塞在条件变量cond上的线程。这些线程被唤醒后将再次竞赛相应的互斥锁,所以有必要当心运用这个函数。
3 信号量
信号量本质上是一个非负的整数计数器,它被用来操控对公共资源的拜访。当公共资源添加时,调用函数sem_post()添加信号量。只要当信号量值大于0时,才干运用公共资源,运用后,函数sem_wait()削减信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起相同的效果,它是函数sem_wait()的非堵塞版别。下面逐一介绍和信号量有关的一些函数,它们都在头文件/usr/include/semaphore.h中界说。
信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间同享,不然只能为当时进程的一切线程同享;value给出了信号量的初始值。
函数sem_post( sem_t *sem )用来添加信号量的值。当有线程堵塞在这个信号量上时,调用这个函数会使其间的一个线程不再堵塞,挑选机制相同是由线程的调度战略决议的。
函数sem_wait( sem_t *sem )被用来堵塞当时线程直到信号量sem的值大于0,免除堵塞后将sem的值减一,标明公共资源经运用后削减。函数sem_trywait ( sem_t *sem )是函数sem_wait()的非堵塞版别,它直接将信号量sem的值减一。
函数sem_destroy(sem_t *sem)用来开释信号量sem。
下面来看一个运用信号量的比如。在这个比如中,一共有4个线程,其间两个线程担任从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/* File sem.c */
#include
#include
#include
#define MAXSTACK 100
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 从文件1.dat读取数据,每读一次,信号量加一*/
void ReadData1(void){
FILE *fp=fopen(“1.dat”,“r”);
while(!feof(fp)){
fscanf(fp,“%d %d”,&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*从文件2.dat读取数据*/
void ReadData2(void){
FILE *fp=fopen(“2.dat”,“r”);
while(!feof(fp)){
fscanf(fp,“%d %d”,&stack[size][0],&stack[size][1]);
sem_post(&sem);
++size;
}
fclose(fp);
}
/*堵塞等候缓冲区有数据,读取数据后,开释空间,持续等候*/
void HandleData1(void){
while(1){
sem_wait(&sem);
printf(“Plus:%d+%d=%d\n”,stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
–size;
}
}
void HandleData2(void){
while(1){
sem_wait(&sem);
printf(“Multiply:%d*%d=%d\n”,stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
–size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);
pthread_create(&t1,NULL,(void *)HandleData1,NULL);
pthread_create(&t2,NULL,(void *)HandleData2,NULL);
pthread_create(&t3,NULL,(void *)ReadData1,NULL);
pthread_create(&t4,NULL,(void *)ReadData2,NULL);
/* 避免程序过早退出,让它在此无限期等候*/
pthread_join(t1,NULL);
}
在Linux下,用指令gcc -lpthread sem.c -o sem生成可履行文件sem。事前修正好数据文件1.dat和2.dat,假定它们的内容别离为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,运转sem,得到如下的成果:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11
从中能够看出各个线程间的竞赛联系。而数值并未按原先的次第显示出来,这是因为size这个数值被各个线程恣意修正的原因。这也往往是多线程编程要留意的问题。