title: 线程(二)————线程同步与互斥
date: 2019-08-11 16:08:18
tags: [Linux,线程]
categories: Linux
文章目录
1、线程的同步与互斥
同步机制: 多个进程或线程按照一定的先后顺序来执行 。 一般使用pv操作 针对的对象是 信号量
互斥机制: 主要能够对临界资源进行保护,(保护临资源的完整性 )临界 资源可以是一小段的代码片段 ,保证同一时刻 只能有一个进程来运行被保护的代码片段。 互斥机制 一般是加互斥锁。
2、互斥锁(也称互斥量)
在 Posix Thread 中定义了一套专门用于线程互斥的 mutex 函数。 mutex 是一种简单的加锁的方法来控制对共享资源的存取,这个互斥锁只有两种状态(上锁和解锁),可以把互斥锁看作某种意义上的全局变量。为什么需要加锁,就是因为多个线程共用进程的资源,要访问的是公共区间时(全局变量),当一个线程访问的时候,需要加上锁以防止另外的线程对它进行访问,以实现资源的独占。在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程才能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
-
锁机制保护 临界资源 的完整性
-
当锁机制被触发的时候,其余线程会被挂起 ,直到锁被释放掉 会解除挂起状态 。
3、初始化(创建)锁
pthread_mutex_t +变量名;
函数原型 | int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); |
---|---|
功能 | 动态的初始化一般锁 ------常用 |
参数 | restrict mutex:申请的锁,一般会定义成全局性的变量。锁变量的地址 attr:关于锁的属性 ,可以给NULL 表示默认属性 。 |
返回值 | 成功:0 失败:返回错误号 |
互斥锁的属性(第二个参数)有三个值可以选择:
①、PTHREAD_MUTEX_TIMED_NP,这是缺省值(直接写NULL),也就是普通锁(或快速锁)。当一个线程加锁以后,其余请求锁的线程将形成一个阻塞等待队列,并在解锁后按优先级获得解锁。这种锁策略保证了资源分配的公平性。
②、PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁(递归锁),允许同一线程对同一个锁成功获得多次,并通过多次unclock解锁。如果是不同的线程请求,则在加锁线程解锁时重新竞争。
③、PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个进程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
pthread_mutex_t的初始化 | pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
---|---|
功能 | 静态的方式 来申请并初始化一把锁 。 |
4、加锁
操作共享数据之前加锁、加锁的临界区域尽可能的小。
函数原型 | int pthread_mutex_lock(pthread_mutex_t *mutex); |
---|---|
参数 | mutex:要加的锁。阻塞的锁定互斥锁。 要加的锁没有被锁:当前进程将其上锁 被上锁了:阻塞等待该锁被解锁,线程解除阻塞。再将其加锁. |
以下函数不怎么使用
函数原型 | int pthread_mutex_trylock(pthread_mutex_t *mutex); |
---|---|
参数 | mutex:要加的锁。非阻塞的锁定互斥锁。 要加的锁没有被锁:当前进程将其上锁 被上锁了:不会阻塞,返回0:尝试加锁成功;返回-1:不会阻塞做其他事 |
函数原型 | int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict tsptr) |
---|---|
功能 | 与上面函数无差别,只是多了个等待时间参数 |
5、解锁
函数原型 | int pthread_mutex_unlock(pthread_mutex_t *mutex); |
---|---|
参数 | mutex:要解的锁 |
6、销毁锁
函数原型 | int pthread_mutex_destroy(pthread_mutex_t *mutex); |
---|---|
参数 | 要销毁的锁 |
7、死锁
死锁:是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程(线程)在无外力协助下,永远分配不到必须的资源而无法继续运行,这就产生了一种特殊现象死锁。
案例:不加锁
两个线程共同执行fun函数,不加锁的情况下。线程1和线程2共同抢占fun函数,就会出现下面的结果
案例:在上面的基础上加上锁
加锁以后线程1执行fun时被加锁,线程2再来执行fun时会被阻塞,直到线程1执行完毕,解锁线程2才能执行fun
8、原子操作
原子操作(atomic operation)意为“不可被中断的一个或一系列操作”。假设有一片段代码,进程1运行到这段代码的中间时CPU被其他进程2抢走了。其他进程2在获得CPU使用权时有可能对这片代码段进行操作,而我们希望进程1执行完才能让其他进程执行。因此我们可以在这段代码前加上互斥锁,当进程1执行到这段代码时上锁,即使未执行完失去CPU使用权,其他进程也不能对这片代码段进程操作。当进程1再次获取CPU使用权后执行这段代码,解锁后其他进程才能访问这段代码。这样的操作称为原子操作。
9、读写锁
它是一把锁,称为读写锁。只是读/写时加锁的函数不一样。类型为pthread_rwlock_t lock;类型
读写锁的特性:
- 线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功
- 线程A加写锁成功,又来了三个线程,做读操作,三个线程被阻塞
- 读写不能同时进行、写的优先级高。
读时共享、写时独占、写优先级高
pthread_rwlock_init //读写锁的初始化
pthread_rwlock_destroy //销毁读写锁
pthread_rwlock_rdlock //加读锁
pthread_rwlock_wrlock //加写锁
pthread_rwlock_tryrdlock //尝试加读锁
pthread_rwlock_trywrlock //尝试加写锁
pthread_rwlock_unlock //解锁