12.5 同步
如何实现不一样多线程中的切换?如何保护那些共享的变量?
一、信号量机制:使用信号量实现同步
两组函数接口用于信号量:1)、取自POSIX的实时扩展,用于线程;2)、系统V信号量,用于进程的同步。
荷兰计算机科学家Dijkstra首先提出信号量的概念,信号量是一个特殊类型的变量,他能够被增长或者减小,但对其的关键访问被保证是原子操做,即便在一个多线程的程序中也是如此。则意味着若是一个程序中两个或者多个线程视图改变同一个信号量的值,系统将保证执行的顺序都是依次执行。
1)、最简单的信号量:二进制信号量
它只有0和1两个取值。
2)、计数型信号量:
他能够有更大范围的取值。
信号量通常用来保护一段代码,使其每次只能被一个线程访问,此时须要二进制信号量。有时但愿容许有限的线程同时访问执行一段代码,此时须要计数型信号量。
信号量函数的名字都以sem_开头,线程中使用的基本信号量函数有4个:
程序员
#include <semaphore.h> int sem_init(sem_t *sem, int pshared,unsigned int value);
这个函数初始化sem指向的信号量的值,设置它的共享选项,并给他一个初始的整数值。pshared参数控制信号量的类型;若是它的值为0,就表示这个信号量是当前进程的局部信号量,不然这个信号量就能够在多个进程间共享。
数组
#include <semaphore.h> int sem_wait(sem_t * sem); int sem_post(sem_t* sem);
两个函数都以一个指针为参数,该指针指向的对象是由sem_init调用初始化的信号量。
sem_post函数的做用是以原子操做的方式给信号量的值加1。所谓原子操做是指,若是两个线程企图同时给一个信号量加1,它们之间不会互相干扰,而不像若是两个程序同时对同一个文件进行读取、增长、写入操做时可能会引发冲突。信号量的值老是会被正确地加2,由于有两个线程试图改变它。
sem_wait函数以原子操做的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操做。所以,若是对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1。若是对值为0的信号量调用sem_wait,这个函数就会等待,直到有其余线程增长了该信号量的值使其再也不是0为止。若是两个线程同时在sem_wait调用上等待同一个信号量变为非零值,那么当该信号量被第三个线程增长1时,只有其中一个等待线程将开始对信号量减1,而后继续执行,另一个线程还将继续等待。
最后一个信号量函数是sem_destroy。这个函数的做用是,用完信号量后对它进行清理。它的定义以下:
多线程
#include <semaphore.h>
int sem_destory(sem_t * sem);
与前几个函数同样,这个函数也以一个信号量指针为参数,并清理该信号量拥有的全部资源。若是企图清理的信号量正被一些线程等待,就会收到一个错误。
实验一:使用信号量进行同步:函数
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <semaphore.h> 5 #include <pthread.h> 6 #include <string.h> 7 void * thread_function(void* arg); 8 sem_t bin_sem; 9 #define WORK_SIZE 1024 10 char work_area[WORK_SIZE]; 11 int main(int argc, char const *argv[]) 12 { 13 int res; 14 pthread_t a_thread; 15 void* thread_result; 16 //初始化信号量 17 res=sem_init(&bin_sem,0,0); 18 if(res!=0) 19 { 20 perror("Semaphore init failed"); 21 exit(EXIT_FAILURE); 22 } 23 //建立一个线程 24 res=pthread_create(&a_thread,NULL,thread_function,NULL); 25 if(res!=0) 26 { 27 perror("Create thread failed"); 28 exit(EXIT_FAILURE); 29 } 30 printf("input some text. Enter 'end' to finish\n"); 31 while(strncmp(work_area,"end",3)!=0) 32 { 33 fgets(work_area,WORK_SIZE,stdin); 34 printf("建立者释放一个信号量\n"); 35 sem_post(&bin_sem);//释放一个信号量 36 } 37 printf("\nWaiting for thread to finish\n"); 38 res=pthread_join(a_thread,&thread_result); 39 if(res!=0) 40 { 41 perror("Thread join failed"); 42 exit(EXIT_FAILURE); 43 } 44 printf("Thread joined\n"); 45 sem_destroy(&bin_sem); 46 exit(EXIT_SUCCESS); 47 return 0; 48 } 49 void* thread_function(void* arg) 50 { 51 printf("线程申请信号量\n"); 52 sem_wait(&bin_sem); 53 printf("申请信号量成功\n"); 54 while(strncmp("end",work_area,3)!=0) 55 { 56 printf("You input %d :characters\n",(int)strlen(work_area)); 57 printf("线程申请信号量\n"); 58 sem_wait(&bin_sem); 59 printf("申请信号量成功\n"); 60 } 61 pthread_exit(NULL); 62 }
该程序有一个全局变量:work_area,用来接受用户的输入;建立一个线程用来输出用户输入的字符串的长度。
建立线程后,线程执行thread_function,申请信号量,此时,在主程序中,用户尚未书输入数据,线程进入阻塞状态,当用户输入数据后,主程序释放一个信号量,此时线程成功的获得了信号量,就能够执行后面的代码。
二、使用互斥量进行同步
互斥量容许程序员锁住某个对象,使得每次只能有一个线程访问。为了控制对关键代码的访问,必须在进入这段代码以前锁住一个互斥量,而后在完成操做以后解锁它。
函数定义: post
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t* mutexattr); int pthread_mutex_lock(pthread_mutex_t* mutex); int pthread_mutex_unlock(pthread_mutex_t* mutex); int pthread_mutex_destory(pthread_mutex_t* mutex);
与其余函数同样,成功时返回0,失败时将返回错误代码,但这些函数并不设置errno,你必须对函数的返回代码进行检查。
与信号量相似,这些函数的参数都是一个先前声明过的对象的指针。对互斥量来讲,这个对象的类型为pthread_mutex_t。pthread_mutex_init函数中的属性参数容许咱们设置互斥量的属性,而属性控制着互斥量的行为。属性类型默认为fast,但它有一个小缺点:若是程序试图对一个已经加了锁的互斥量调用pthread_mutex_lock,程序就会被阻塞,而又由于拥有互斥量的这个线程正是如今被阻塞的线程,因此互斥量就永远也不会被解锁了,程序也就进入死锁状态。这个问题能够经过改变互斥量的属性来解决,咱们可让它检查这种状况并返回一个错误,或者让它递归的操做,给同一个线程加上多个锁,但必须注意在后面执行同等数量的解锁操做。
实验二:spa
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<semaphore.h> 6 #include<pthread.h> 7 void* thread_function(void*arg); 8 pthread_mutex_t work_mutex; 9 #define WORK_SIZE 1024 10 char work_area[WORK_SIZE]; 11 int time_to_exit=0; 12 int main() 13 { 14 int res; 15 pthread_t a_thread; 16 void* thread_res; 17 res=pthread_mutex_init(&work_mutex,NULL);//初始化互斥量 18 if(res!=0) 19 { 20 perror("Init mutex failed"); 21 exit(EXIT_FAILURE); 22 } 23 //建立线程 24 res=pthread_create(&a_thread,NULL,thread_function,NULL); 25 if(res!=0) 26 { 27 perror("Create thread failed"); 28 exit(EXIT_FAILURE); 29 } 30 //锁住互斥量 31 pthread_mutex_lock(&work_mutex); 32 printf("Input some text. Ente 'end' to finish\n"); 33 while(!time_to_exit) 34 { 35 fgets(work_area,WORK_SIZE,stdin); 36 pthread_mutex_unlock(&work_mutex);//解锁互斥量 37 while(1) 38 { 39 pthread_mutex_lock(&work_mutex); 40 if(work_area[0]!='\0') 41 { 42 pthread_mutex_unlock(&work_mutex); 43 sleep(1); 44 } 45 else 46 break; 47 } 48 } 49 pthread_mutex_unlock(&work_mutex); 50 printf("\nWaiting for thread to finish...\n"); 51 res=pthread_join(a_thread,&thread_res); 52 if(res!=0) 53 { 54 perror("Thread join error"); 55 exit(EXIT_FAILURE); 56 } 57 printf("Thread joined\n"); 58 pthread_mutex_destroy(&work_mutex); 59 exit(EXIT_SUCCESS); 60 return 0; 61 } 62 void* thread_function(void* arg) 63 { 64 sleep(1); 65 pthread_mutex_lock(&work_mutex); 66 while(strncmp("end",work_area,3)!=0) 67 { 68 printf("You input %d characters\n",(int)strlen(work_area)-1); 69 work_area[0]='\0'; 70 pthread_mutex_unlock(&work_mutex); 71 sleep(1); 72 pthread_mutex_lock(&work_mutex); 73 while(work_area[0]=='\0') 74 { 75 pthread_mutex_unlock(&work_mutex); 76 sleep(1); 77 pthread_mutex_lock(&work_mutex); 78 } 79 } 80 time_to_exit=1; 81 work_area[0]='\0'; 82 pthread_mutex_unlock(&work_mutex); 83 pthread_exit(0); 84 }
新线程首先试图对互斥量加锁。若是它已经被锁住,这个调用将被阻塞直到它被释放为止。一旦得到访问权,咱们就检查是否有申请退出程序的请求。若是有,就设置time_to_exit变量,再把工做区的第一个字符设置为\0,而后退出。
若是不想退出,就统计字符个数,而后把work_area数组中的第一个字符设置为null。咱们用将第一个字符设置为null的方法通知读取输入的线程,咱们已完成了字符统计。而后解锁互斥量并等待主线程继续运行。咱们将周期性地尝试给互斥量加锁,若是加锁成功,就检查是否主线程又有字符送来要处理。若是尚未,就解锁互斥量继续等待;若是有,就统计字符个数并再次进入循环。
线程