线程的同步与互斥

一、mutex(互斥量)

·大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量

·但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完后线程之间的交互

·多个线程并发的操作共享变量,会带来问题

例:操作共享变量会有问题的售票系统

  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<pthread.h>
  6 
  7 int ticket = 100;
  8 void* route(void *arg)
  9 {
 10     char* id = (char*)arg;
 11     while(1){
 12         if(ticket > 0){
 13             sleep(1);
 14             printf("%s sells ticket:%d\n",id,ticket);
 15             ticket--;
 16         }
 17         else{
 18             break;
 19         }
 20     }
 21 }
 22 int main()
 23 {
 24     pthread_t t1,t2,t3,t4;
 25 
 26     pthread_create(&t1,NULL,route,"thread1");
 27     pthread_create(&t2,NULL,route,"thread2");
 28     pthread_create(&t3,NULL,route,"thread3");
 29     pthread_create(&t4,NULL,route,"thread4");
 30 
 31     pthread_join(t1,NULL);
 32     pthread_join(t2,NULL);
 33     pthread_join(t3,NULL);
 34     pthread_join(t4,NULL);
 35 }


为什么会对卖3张呢?

·if语句判断条件为真以后,代码可以并发的切换到其他线程

·sleep这个模拟漫长业务过程,在这个漫长的过程中,可能有其他很多的线程进入该代码段

·--ticket操作本身就不是一个原子操作

--操作并不是原子操作,而是对应三条汇编指令

·load:将共享变量ticket从内存加载到寄存器中

·update:更新寄存器里面的值,执行-1操作

·store:将新值从寄存器写回共享变量ticket的内存地址

解决上面问题就必须做到:

·代码必须要有互斥行为,当代码进入临界区执行时,不允许其他线程进入该临界区

·如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区

·如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点就必须要有一把锁。Linux上提供的这把锁叫互斥量


二、互斥量的接口

1、初始化

方法一:静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

方法二:动态分配


参数:mutex——要初始化的互斥量

          attr——NULL

2、销毁互斥量

注意:

·使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁、

·不要销毁一个已经加锁的互斥量

·已经销毁的互斥量,要确保后面不会有线程尝试加锁


3、互斥量加锁和解锁


返回值:成功返回0,失败返回错误码

调用pthread_mutex_lock会遇到的情况:

·互斥量处于未锁状态,该函数会将互斥量锁定,同时成功返回

·发起函数调用时,其他线程已经锁住互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞,等待互斥量解锁

改进上面的售票代码

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<pthread.h>
  6 #include<sched.h>
  7 
  8 int ticket = 100;
  9 pthread_mutex_t mutex;
 10 
 11 void* route(void *arg)
 12 {
 13     char* id = (char*)arg;
 14     while(1){
 15         pthread_mutex_lock(&mutex);
 16         if(ticket > 0){
 17             sleep(1);
 18             printf("%s sells ticket:%d\n",id,ticket);
 19             ticket--;
 20             pthread_mutex_unlock(&mutex);
 21         }
 22         else{
 23             pthread_mutex_unlock(&mutex);
 24             break;
 25         }
 26     }
 27 }
 28 int main()
 29 {
 30     pthread_t t1,t2,t3,t4;
 31 
 32     pthread_mutex_init(&mutex,NULL);
 33 
 34     pthread_create(&t1,NULL,route,"thread1");
 35     pthread_create(&t2,NULL,route,"thread2");
 36     pthread_create(&t3,NULL,route,"thread3");
 37     pthread_create(&t4,NULL,route,"thread4");
 38 
 39     pthread_join(t1,NULL);
 40     pthread_join(t2,NULL);
 41     pthread_join(t3,NULL);
 42     pthread_join(t4,NULL);
 43 
 44     pthread_mutex_destroy(&mutex);
 45 }


三、条件变量

·当一个线程互斥的访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了

·例如一个线程访问队列时,发现队列为空,它只能等,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量

条件变量函数

1、初始化


参数:cond——要初始化的条件变量

          attr——NULL

2、销毁


3、等待条件满足


参数:cond——要在这个条件变量上等待

          mutex——互斥量

4、唤醒等待


  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<pthread.h>
  6 
  7 pthread_cond_t cond;
  8 pthread_mutex_t mutex;
  9 
 10 void *r1(void *arg)
 11 {
 12     while(1){
 13         pthread_cond_wait(&cond,&mutex);
 14         printf("active\n");
 15     }
 16 }
 17 void *r2(void *arg)
 18 {
 19     pthread_cond_signal(&cond);
 20     sleep(1);
 21 }
 22 int main()
 23 {
 24     pthread_t t1, t2;
 25 
 26     pthread_cond_init(&cond,NULL);
 27     pthread_mutex_init(&mutex,NULL);
 28 
 29     pthread_create(&t1,NULL,r1,NULL);
 30     pthread_create(&t2,NULL,r2,NULL);
 31 
 32     pthread_join(t1,NULL);
 33     pthread_join(t2,NULL);
 34 
 35     pthread_mutex_destroy(&mutex);
 36     pthread_cond_destroy(&cond);
 37 }

pthread_cond_wait需要互斥量的原因:

(1)条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程

(2)条件变量不会无缘无故的突然就满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据


按照上面的说法,我们设计出:先上锁,发现条件不满足,解锁,然后等待条件变量上

//错误的设计
pthread_mutex_lock(&mutex);
while(condition){
     pthread_mutex_unlock(&mutex);
     //解锁后,等待之前,条件可能已经满足,信号发出,但是该信号可能被错过
     pthread_cond_wait(&cond);
}
pthread_mutex_unlock(&mutex);

·由于解锁和等待不是原子操作。调用解锁之后pthread_mutex_unlock之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_cond_wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是原子操作

·int pthread_cond_wait(pthrad_cond_t *cond,pthread_mutex_t *mutex);进入该函数后,会去看条件变量等于0不?等于,把互斥量变为1,直到cond_wait返回,把条件变量改为1,把互斥量恢复成原来的样子

五、条件变量的使用范围

1、等待条件代码

pthread_mutex_lock(&mutex)
while(条件为假)
    pthread_cond_waiit(cond,mutex);
修改条件
pthread_mutex_unlock(&mutex);

2、给条件发送信号代码

pthread_mutex_lock(&mutex);
设置条件
pthread_cond_signal(cond);
pthread_mutex_unclock(&mutex);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值