Linux下的多线程编程二(线程的同步与互斥)

Linux下的多线程编程一(线程的基础知识)

一、什么叫做线程的同步与互斥?为什么需要同步与互斥?

1、同步与互斥
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

2、线程的同步与异步
线程同步是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率低 。
线程异步:访问资源时在空闲等待时同时访问其他资源,实现多线程机制 。异步处理就是,你现在问我问题,我可以不回答你,等我用时间了再处理你这个问题。

同步信息被立即处理 – 直到信息处理完成才返回消息句柄;
异步信息收到后将在后台处理一段时间 – 而早在信息处理结束前就返回消息句柄。

3、多线程的同步与互斥的区别
假如把整条道路看成是一个【进程】的话,那么马路中间白色虚线分隔开来的各个车道就是进程中的各个【线程】了。
①这些线程(车道)共享了进程(道路)的公共资源(土地资源)。
②这些线程(车道)必须依赖于进程(道路),也就是说,线程不能脱离于进程而存在(就像离开了道路,车道也就没有意义了)。
③这些线程(车道)之间可以并发执行(各个车道你走你的,我走我的),也可以互相同步(某些车道在交通灯亮时禁止继续前行或转弯,必须等待其它车道的车辆通行完毕)。
④这些线程(车道)之间依靠代码逻辑(交通灯)来控制运行,一旦代码逻辑控制有误(死锁,多个线程同时竞争唯一资源),那么线程将陷入混乱,无序之中。
⑤这些线程(车道)之间谁先运行是未知的,只有在线程刚好被分配到CPU时间片(交通灯变化)的那一刻才能知道。

注:由于用于互斥的信号量sem与所有的并发进程有关,所以称之为公有信号量。公有信号量的值反映了公有资源的数量。只要把临界区置于P(sem)和V(sem)之间,即可实现进程间的互斥。就象火车中的每节车厢只有一个卫生间,该车厢的所有旅客共享这个公有资源:卫生间,所以旅客间必须互斥进入卫生间,只要把卫生间放在P(sem)和V(sem)之间,就可以到达互斥的效果。

二、mutex(互斥量)

1、多线程的冲突举例
我们知道,多个线程同时访问共享数据时,可能会产生冲突。比如两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成:
1> 从内存读变量值到寄存器
2>寄存器的值加1
3>将寄存器的值写回内存
假设两个线程在多处理器平台上同时执行这三条指令,则可能导致下图所示的结果,最后变量只加了一次而非两次。
这里写图片描述

数据二义性产生原因:不同的线程访问同一份临界资源,其次线程的操作不是原子操作。
为了验证多线程同时访问临界资源产生的冲突与数据的二义性,创建两个线程,各自把global增加5000次,正常情况下最后global应该等于10000,但事实上每次运行该程序的结果都不一样,代码如下(为了使现象更容易观察到,我们把上述三条指令做的事情用更多条指令来做):

  1 /************************************** 
  2 *文件说明:mutex.c 
  3 *作者:段晓雪 
  4 *创建时间:2017年05月29日 星期一 16时11分42秒 
  5 *开发环境:Kali Linux/g++ v6.3.0 
  6 ****************************************/ 
  7 #include<stdio.h> 
  8 #include<stdlib.h> 
  9 #include<pthread.h> 
 10  
 11 int global = 0;//定义全局变量 
 13 {
 14     int i = 5000;//数据太小,线程发生冲突的概率比较小
 15     //int i = 5000000;//1、增大循环次数,发生冲突较多
 16     //while(i--)
 17     //{
   
 18     //  global++;
 19     //}
 20     //2、增多线程切换:内核态---->用户态,增大冲突概率
 21     while(i--)
 22     {
 23         int tmp = global;
 24         printf("%d\n",global);
 25         global = tmp + 1;
 26     }
 27     //printf("\n");
 28     return (void*)0;
 29 }
 30 int main()
 31 {
 32     pthread_t tid1;
 33     pthread_t tid2;
 34     pthread_create(&tid1,NULL,pthread_add,NULL);//创建线程1
 35     pthread_create(&tid2,NULL,pthread_add,NULL);//创建线程2
 36     pthread_join(tid1,NULL);//等待线程1
 37     pthread_join(tid2,NULL);//等待线程2
 38     printf("the global is %d\n",global);
 39     return 0;
 40 }

第一次运行结果—-5101:
这里写图片描述

第二次运行结果—-5091:
这里写图片描述

第三次运行结果—-5000:
这里写图片描述

3次运行结果均不一样,足以说明多线程在同步访问全局变量global时发生了冲突。

2、解决多线程的冲突问题 —-引入互斥锁
(1)互斥锁:(Mutex,Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得 锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。

(2)互斥锁的创建和销毁:
Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁。
1>定义一把锁

pthread_mutex_t lock;

2>互斥锁lock的初始化
如果lock是局部变量—可用初始化函数pthread_mutex_init初始化。

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

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

如果lock是静态或全局变量—可用 PTHREAD_MUTEX_INITIALIZER初始化 ,相当于用pthread_mutex_init初始化并且attr参数为NULL。

3>加锁与解锁

int pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞式加锁
int pthread_mutex_lock(pthread_mutex_t* mutex)//阻塞式加锁
int pthread_mutex_unlock(pthread_mutex_t* mutex)//解锁

如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被 另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
Mutex的两个基本操作lock和unlock是如何实现的呢?
假设Mutex变量 的值为1表示互斥锁空闲,这时某个进程调用lock可以获得锁,而Mutex的值为0表示互斥锁已经被某个线程获得,其它线程再调用lock只能挂起等待。

4>销毁锁资源

int pthread_mutex_destroy(pthread_mutex_t* mutex);

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

(3)解决多线程的冲突问题

   #include<stdio.h>
   #include<stdlib.h>
   #include<pthread.h>

   int 
  • 3
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值