线程同步与互斥
线程共享进程的资源和地址空间,对这些资源进行操作时,必须考虑线程间同步与互斥问题
三种线程同步机制
•互斥锁
•信号量
•条件变量
互斥锁更适合同时可用的资源是惟一的情况
信号量更适合同时可用的资源为多个的情况
互斥锁
用简单的加锁方法控制对共享资源的原子操作
只有两种状态: 上锁、解锁
可把互斥锁看作某种意义上的全局变量
在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作
若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止
互斥锁保证让每个线程对共享资源按顺序进行原子操作
互斥锁分类
区别在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待
快速互斥锁
•调用线程会阻塞直至拥有互斥锁的线程解锁为止
•默认为快速互斥锁
检错互斥锁
•为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息
互斥锁主要包括下面的基本函数:
互斥锁初始化:pthread_mutex_init()
互斥锁上锁:pthread_mutex_lock()
互斥锁判断上锁:pthread_mutex_trylock()
互斥锁解锁:pthread_mutex_unlock()
消除互斥锁:pthread_mutex_destroy()
View Code #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUM 3 #define REPEAT_TIMES 5 #define DELAY 4 pthread_mutex_t mutex; void *thrd_func(void *arg); int main(){ pthread_t thread[THREAD_NUM]; int no; void *tret; srand((int)time(0)); // 创建快速互斥锁(默认),锁的编号返回给mutex pthread_mutex_init(&mutex,NULL); // 创建THREAD_NUM个线程,每个线程号返回给&thread[no],每个线程的入口函数均为thrd_func,参数为 for(no=0;no<THREAD_NUM;no++){ if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) { printf("Create thread %d error!\n",no); exit(1); } else printf("Create thread %d success!\n",no); } // 对每个线程进行join,返回值给tret for(no=0;no<THREAD_NUM;no++){ if (pthread_join(thread[no],&tret)!=0){ printf("Join thread %d error!\n",no); exit(1); }else printf("Join thread %d success!\n",no); } // 消除互斥锁 pthread_mutex_destroy(&mutex); return 0; } void *thrd_func(void *arg){ int thrd_num=(void*)arg; // 传入的参数,互斥锁的编号 int delay_time,count; // 对互斥锁上锁 if(pthread_mutex_lock(&mutex)!=0) { printf("Thread %d lock failed!\n",thrd_num); pthread_exit(NULL); } printf("Thread %d is starting.\n",thrd_num); for(count=0;count<REPEAT_TIMES;count++) { delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1; sleep(delay_time); printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time); } printf("Thread %d is exiting.\n",thrd_num); // 解锁 pthread_mutex_unlock(&mutex); pthread_exit(NULL); }
信号量
操作系统中所用到的PV原子操作,广泛用于进程或线程间的同步与互斥
•本质上是一个非负的整数计数器,被用来控制对公共资源的访问
PV原子操作:对整数计数器信号量sem的操作
•一次P操作使sem减一,而一次V操作使sem加一
•进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限
–当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限
–当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止
PV操作主要用于线程间的同步和互斥
互斥,几个线程只设置一个信号量sem
同步,会设置多个信号量,安排不同初值来实现它们之间的顺序执行
信号量函数
sem_init() 创建一个信号量,并初始化它
sem_wait()和sem_trywait(): P操作,在信号量大于零时将信号量的值减一
•区别: 若信号量小于零时,sem_wait()将会阻塞线程,sem_trywait()则会立即返回
sem_post(): V操作,将信号量的值加一同时发出信号来唤醒等待的线程
sem_getvalue(): 得到信号量的值
sem_destroy(): 删除信号量
View Code #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define THREAD_NUM 3 #define REPEAT_TIMES 5 #define DELAY 4 sem_t sem[THREAD_NUM]; void *thrd_func(void *arg); int main(){ pthread_t thread[THREAD_NUM]; int no; void *tret; srand((int)time(0)); // 初始化THREAD_NUM-1个信号量,均初始化为0 for(no=0;no<THREAD_NUM-1;no++){ sem_init(&sem[no],0,0); } // sem[2]信号量初始化为1,即sem数组中最后一个信号量 sem_init(&sem[2],0,1); // 创建THREAD_NUM个线程,入口函数均为thrd_func,参数为(void*)no for(no=0;no<THREAD_NUM;no++){ if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) { printf("Create thread %d error!\n",no); exit(1); } else printf("Create thread %d success!\n",no); } // 逐个join掉THREAD_NUM个线程 for(no=0;no<THREAD_NUM;no++){ if (pthread_join(thread[no],&tret)!=0){ printf("Join thread %d error!\n",no); exit(1); }else printf("Join thread %d success!\n",no); } // 逐个取消信号量 for(no=0;no<THREAD_NUM;no++){ sem_destroy(&sem[no]); } return 0; } void *thrd_func(void *arg){ int thrd_num=(void*)arg; // 参数no int delay_time,count; // 带有阻塞的p操作 sem_wait(&sem[thrd_num]); printf("Thread %d is starting.\n",thrd_num); for(count=0;count<REPEAT_TIMES;count++) { delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1; sleep(delay_time); printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time); } printf("Thread %d is exiting.\n",thrd_num); // 对前一个信号量进行V操作 // 由于只有最后一个信号量初始化为1,其余均为0 // 故线程执行的顺序将为逆序 sem_post(&sem[(thrd_num+THREAD_NUM-1)%THREAD_NUM]); pthread_exit(NULL); // 线程主动结束 }