多线程已然成为服务器开发不可或缺的重要知识点了,这么如何协调各个线程之间的工作就显得至关重要,于是这篇文章就来总结一下线程同步的方式。
哪些是线程同步?
“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后顺序运行。线程同步:即当有一个线程在对显存进行操作时,其他线程都不可以对这个显存地址进行操作,直至该线程完成操作,其他线程能够对该显存地址进行操作,而其他线程又处于等待状态。
“同步”的目的,是为了防止数据混乱,解决与时间有关的错误。实际上,除了线程间须要同步,进程间、信号间等等都须要同步机制。
为此,所有“多个控制流,共同操作一个共享资源”的情况,都须要同步。
这么如何达到线程同步就须要一些技巧,这儿讲解了互斥量,条件变量,讯号量线程同步方式。
互斥锁mutex
互斥锁的原理很容易理解,我们给一个共享资源分配一把锁,每位线程在对资源操作前都尝试先加锁,成功加锁能够操作,操作结束解锁。
通过这样:资源还是共享的,线程间也还是竞争的,但通过“锁”就将资源的访问弄成互斥操作,而后与时间有关的错误也不会再形成了。但,应注意:同一时刻,只能有一个线程持有该锁。
我们先用一个用互斥量使用步骤开始讲解:
1,pthread_mutex_t函数,创造一个互斥锁。
2,pthread_mutex_init函数,初始化。
3,pthread_mutex_lock/pthread_mutex_trylock函数,加锁。
4,访问共享数据
5,pthread_mutex_unlock函数,解锁
6,pthread_mutex_destroy函数,销毁锁
pthread_mutex_tmutex;
没啥好说的,就是创造一个互斥锁mutex。
pthread_mutex_init函数
intpthread_mutex_init(pthread_mutex_t*restrictmutex,constpthread_mutexattr_t*restrictattr);函数作用是:初始化一个互斥锁(互斥量)--->年率可看作1
参1:传出参数红帽linux系统下载,调用时应传&mutex。
restrict关键字:只用于限制表针,告诉编译器linux线程间同步方式,所有更改该表针指向显存中内容的操作,只能通过本表针完成。不能通过除本表针以外的其他变量或表针更改。
参2:互斥量属性。是一个传入参数,一般传NULL,选用默认属性(线程间共享)。参APUE.12.4同步属性
pthread_mutex_lock函数
intpthread_mutex_lock(pthread_mutex_t*mutex);函数作用是:加锁。可理解为将mutex--(或-1),操作后mutex的值为0。
另外,lock与trylock的区别是:lock加锁失败会造成阻塞,trylock加锁失败不会阻塞会返回-1。
pthread_mutex_unlock函数
intpthread_mutex_unlock(pthread_mutex_t*mutex);解锁。可理解为将mutex++(或+1),操作后mutex的值为1。
pthread_mutex_destroy函数
intpthread_mutex_destroy(pthread_mutex_t*mutex);销毁一个互斥锁
lock与unlock:
lock尝试加锁,倘若加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。
unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤起,至于那个线程先被唤起,取决于优先级、调度。默认:先阻塞、先唤起。
1 #include 2 #include 3 #include 4 #include 5 #include 6 7 //互斥锁设为全局变量 8 pthread_mutex_t mutex; 9 10 //子线程 11 void *tfn(void *arg) { 12 srand(time(NULL)); 13 while (1) { 14 pthread_mutex_lock(&mutex); //加锁,不行则阻塞 15 printf("hello "); 16 printf("worldn"); 17 pthread_mutex_unlock(&mutex); 18 sleep(rand()%3); 19 } 20 return NULL; 21 } 22 23 int main() 24 { 25 pthread_t tid; 26 srand(time(NULL)); 27 28 pthread_mutex_init(&mutex,NULL); //初始化互斥锁 29 30 pthread_create(&tid,NULL,tfn,NULL); //创造子线程 31 32 //主线程 33 while (1) { 34 pthread_mutex_lock(&mutex); //互斥锁加锁,不行则阻塞 35 printf("HELLO "); 36 printf("WORLDn"); 37 pthread_mutex_unlock(&mutex); //互斥锁解锁 38 sleep(rand()%3); 39 } 40 41 //最后记得回收子线程,销毁互斥锁 42 pthread_join(tid,NULL); 43 pthread_mutex_destroy(&mutex); 44 return 0; 45 }
互斥锁测试
互斥锁读写锁
条件变量
条件变量本身不是锁!但它也可以导致线程阻塞。一般与互斥锁配合使用。给多线程提供一个会合的场所。
函数类似互斥锁,我们也从条件变量使用步骤开始:
1,pthread_cond_t函数,用于定义条件变量
2,pthread_cond_init函数
3,pthread_cond_wait/pthread_cond_timedwait函数
4,pthread_cond_signal/pthread_cond_broadcast函数
5,唤起以后,重新申请互斥锁,之后操作共享资源
6,pthread_cond_destroy函数
pthread_cond_init函数
intpthread_cond_init(pthread_cond_t*restrictcond,constpthread_condattr_t*restrictattr);初始化一个条件变量
参1:创造的条件变量参2:attr表条件变量属性,一般为默认值,传NULL即可
pthread_cond_wait函数
intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex);阻塞等待一个条件变量
函数作用:
1.阻塞等待条件变量cond(参1)满足
2.释放已把握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
注意:12.两步为一个原子操作。
3.当被唤起,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);
pthread_cond_timedwait函数
intpthread_cond_timedwait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex,conststructtimespec*restrictabstime);限时等待一个条件变量
与pthread_cond_wait函数相比就是多了structtimespec这个结构体:
structtimespec{
time_ttv_sec;/*seconds*/秒
longtv_nsec;/*nanosecondes*/毫秒,注意这儿是毫秒,timeval这个结构体这儿是毫秒
但是要注意这儿的abstime是绝对时间,所以正确用法是:
time_tcur=time(NULL);获取当前时间。
structtimespect;定义timespec结构体变量t
_sec=cur+1;定时1秒
pthread_cond_timedwait(&cond,&mutex,&t);传参
pthread_cond_signal函数
intpthread_cond_signal(pthread_cond_t*cond);唤起起码一个阻塞在条件变量上的线程
tpthread_cond_broadcast函数
intpthread_cond_broadcast(pthread_cond_t*cond);唤起全部阻塞在条件变量上的线程
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 8 //线程不能perror,有自己独特的错误处理方式 9 void error_thread(int ret,char *str) { 10 if (ret!=0) { 11 fprintf(stderr,"%s:%sn",str,strerror(ret)); 12 pthread_exit(NULL); 13 } 14 } 15 16 //把生产的物品做成链表 17 struct msg{ 18 int num; 19 struct msg *next; 20 }; 21 struct msg *head; 22 23 pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //互斥量 24 pthread_cond_t has_data=PTHREAD_COND_INITIALIZER; //条件变量 25 26 //生产者线程 27 void *producer(void *arg) { 28 while (1) { 29 //生产者生产一个数据 30 struct msg *mp=malloc(sizeof(struct msg)); 31 mp->num = rand()00+1; 32 printf("--produce %dn",mp->num); 33 34 //利用互斥量对公共区域做互斥 35 pthread_mutex_lock(&mutex); //加互斥锁 36 mp->next=head; //写公共区域 37 head=mp; 38 pthread_mutex_unlock(&mutex); //解互斥锁 39 40 //条件变量:唤醒阻塞在条件变量has_data的线程 41 pthread_cond_signal(&has_data); 42 43 sleep(rand()%3); 44 } 45 return NULL; 46 } 47 48 //消费者线程 49 void *consumer(void *arg) { 50 while (1) { 51 struct msg *mp; 52 53 // 54 pthread_mutex_lock(&mutex); 55 while (head==NULL) 56 pthread_cond_wait(&has_data,&mutex); 57 58 //消费生产品,读写公共区域 59 mp=head; 60 head=mp->next; 61 printf("-------------consumer id %lu :%dn",pthread_self(),mp->num); 62 free(mp); 63 64 sleep(rand()%3); 65 } 66 return NULL; 67 } 68 69 int main(int argc,char *argv[]) { 70 int ret; 71 pthread_t pid,cid; 72 73 srand(time(NULL)); 74 75 ret=pthread_create(&pid,NULL,producer,NULL); //生产者 76 if (ret!=0) 77 err_thread(ret,"pthread_create producer error"); 78 79 ret=pthread_create(&cid,NULL,consumer,NULL); //消费者 80 if (ret!=0) 81 err_thread(ret,"pthread_create producer error"); 82 83 return 0; 84 }
条件变量解决生产者消费者问题
从生产者消费者问题我们可以看出条件变量配合使用的一点益处:
相较于mutex而言,条件变量可以降低竞争。
如直接使用mutex,不仅生产者、消费者之间要竞争互斥量以外,消费者之间也须要竞争互斥量,但若果凝聚(数组)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制之后,只有生产者完成生产,才能造成消费者之间的竞争。提升了程序效率。
讯号量
讯号量可以看作是进化版的互斥锁(1-->N),因为互斥锁的细度比较大,假如我们希望在多个线程间对某一对象的部份数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样即使达到了多线程操作共享数据时保证数据正确性的目的,却无形中造成线程的并发性增长。线程从并行执行,弄成了串行执行。与直接使用单进程无异。
讯号量,是相对折中的一种处理方法,既能保证同步,数据不混乱linux web服务器,又能提升线程并发。
同样的,我们以使用步骤开始讲解:
1,sem_tsem;规定讯号量sem不能<0。
2,sem_init函数初始化
3linux线程间同步方式,sem_wait/sem_trywait/sem_timedwait函数,讯号量尝试减一,打算访问共享资源
4,访问操作共享资源
5,sem_post函数,访问完成,讯号量加一
6,sem_destroy函数销毁讯号量
sem_init函数
intsem_init(sem_t*sem,intpshared,unsignedintvalue);初始化一个讯号量
参1:sem讯号量
参2:pshared取0用于线程间;取非0(通常为1)用于进程间
参3:value指定讯号量年率>=0
sem_wait函数
intsem_wait(sem_t*sem);给讯号量加锁--
sem_trytwait函数
intsem_trywait(sem_t*sem);尝试对讯号量加锁--(与sem_wait的区别类比lock和trylock,即阻塞与非阻塞)
sem_timedwait函数
intsem_timedwait(sem_t*sem,conststructtimespec*abs_timeout);限时尝试对讯号量加锁--
参2:abs_timeout采用的是绝对时间。注意这儿也是timespec,参考前面条件变量,也是绝对时间+微秒。
sem_post函数
intsem_post(sem_t*sem);给讯号量解锁++
sem_destroy函数
intsem_destroy(sem_t*sem);销毁一个讯号量
1 #include 2 #include 3 #include 4 #include 5 #include 6 7 #define NUM 5 8 9 int queue[NUM]; 10 sem_t blank_number,product_number; 11 12 void *producer(void *arg) { 13 int i=0; 14 while (1) { 15 //这里是信号量处理生产过程 16 sem_wait(&blank_number); //空位数信号量-- 17 queue[i]=rand()00+1; //开始生产 18 printf("----Produce----%dn",queue[i]); 19 sem_post(&product_number); //产品数信号量++ 20 21 i=(i+1)%NUM; 22 sleep(rand()%1); 23 } 24 } 25 26 void *consumer(void *arg) { 27 int i=0; 28 while (1) { 29 //这里是信号量处理消费过程 30 sem_wait(&product_number); //产品数信号量-- 31 //开始消费 32 printf("---consume--%dn",queue[i]); 33 queue[i]=0; 34 sem_post(&blank_number); //空位数信号量++ 35 36 i=(i+1)%NUM; 37 sleep(rand()%3); 38 } 39 } 40 41 int main(int argc,char *argv[]) 42 { 43 pthread_t pid,cid; 44 45 //参二0为线程1为进程,参三可以理解为信号量初始值 46 sem_init(&blank_number,0,NUM); //空位数 47 sem_init(&product_number,0,0); //产品数 48 49 //创建生产者消费者线程 50 pthread_create(&pid,NULL,producer,NULL); 51 pthread_create(&cid,NULL,consumer,NULL); 52 53 //回收线程 54 pthread_join(pid,NULL); 55 pthread_join(cid,NULL); 56 57 //销毁信号量 58 sem_destroy(&blank_number); 59 sem_destroy(&product_number); 60 61 return 0; 62 }
讯号量解决生产者消费者问题