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

线程的同步与互斥

程序员文章站 2022-05-05 10:29:38
...

一、线程同步

在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,这时会出现程序混乱的问题,无法得到原来需要的数据,所以就要使线程同步。
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态

二、互斥量mutex

1、基本概念

要解决上面的问题,可以引入互斥量,在解决进程间通信所产生的冲突问题时,有一种机制就是信号量,互斥量和信号量基本上是相同的概念,就是当一个线程在访问某个数据时可以加上一把互斥锁,当别的线程也要访问这个数据时就要请求加锁,而此时锁已经被别的线程申请要过去了,那这个线程就需要等待,直到有锁可用才能再加锁访问数据,如此一来,就可以将“读取—执行—写入”这三部化成一个原子性的问题,要么都执行,要么一步也不执行,不会被中途打断。

2、互斥量的接口

(1)初始化互斥量

方法1:静态分配
pthread_mutex mutex=PTHREAD_MUTEX_INITIALIZER
方法2:动态分配
线程的同步与互斥
mutex:要初始化的互斥量
attr:NULL

(2)互斥量销毁

线程的同步与互斥
销毁互斥量需要注意:
(1)使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
(2)不要销毁一个已经加锁的互斥量
(3)已经销毁的互斥量,要确保后面不会有线程再尝试加锁

(3)互斥量加锁和解锁

线程的同步与互斥
返回值:成功返回0,失败返回错误码
调用pthread_ lock 时,可能会遇到以下情况:
(1)互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
(2)发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞,等待互斥量解锁。

三、实例验证

首先我们看一个关于共享变量访问出问题的例子:

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

运行结果:
线程的同步与互斥
可以发现获取的结果-1、-2不是想要的,那造成这个的原因就是因为线程的同步问题造成的:
(1)if 语句判断条件为真以后,代码可以并发的切换到其他线程
(2)usleep这个模拟漫⻓业务的过程,在这个漫⻓的业务过程中,可能有很多个线程会进入该代码段
(3)–ticket操作本⾝就不是⼀个原子操作
要解决上面出现的问题,必须要注意:
(1)代码必须要有互斥行为:当代码进入临界区执⾏行时,不允许其他线程进入该临界区。
(2)如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
(3)如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

对上述代码进行加锁改进:

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

线程的同步与互斥
现在结果就为我们希望看到的了,利用锁解决了同步混乱的问题了。

四、条件变量

1、基本概念

条件变量是多线程间可以通过它来告知其他线程某个状态发生了改变,让等待在这个条件变量的线程继续执行。通俗一点来讲:设置一个条件变量让线程1等待在一个临界区的前面,当其他线程给这个变量执行通知操作时,线程1才会被唤醒,继续向下执行。
条件变量总是和互斥量一起使用,互斥量保护着条件变量,防止多个线程对条件变量产生竞争。

2、相关接口函数

(1)初始化函数

线程的同步与互斥
cond:要初始化的条件变量
attr:NULL

(2)销毁函数

线程的同步与互斥
成功返回0,失败返回错误码

(3)等待函数

线程的同步与互斥
cond:要在这个条件变量上等待
mutex:互斥量

(4)唤醒等待

线程的同步与互斥

3、实例验证

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

运行结果:
线程的同步与互斥

4、条件变量使用小结

使用条件变量,调用signal/broadcast的时候,无法知道是否已经有线程等在wait上了。因此,一般要先改变条件状态,然后再发送signal/broadcast信号。然后在wait调用线程上先检查条件状态,只有当条件状态为假的时候才进入pthread_cond_wait进行等待,从而防止丢失signal/broadcast事件。并且检查条件、pthread_cond_wait,修改条件、signal/broadcast都要在同一个mutex的保护下进行。