欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程的同步与互斥

程序员文章站 2022-05-22 11:21:57
...

一、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);