①为什么需要互斥量
1.共享变量(shared variable): 多个线程共享的变量
2.多个线程访问共享变量所带来的问题
线程A 线程B global_cnt
L
U L 5
S S 6
U 6
当A将寄存器的值+1=6后还未写入全局变量时,线程B读全局变量5到寄存器;这项两个线程操作完后
全局变量的值为6,而分别执行了两次加操作
·Load :将共享变量 global_cnt 从内存加载进寄存器,简称 L 。
·Update :更新寄存器里面的 global_cnt 值,执行加 1 操作,简称 U 。
·Store :将新的值,从寄存器写回到共享变量 global_cnt 的内存地址,简称为 S 。
3.所以要求
临界区内,在同一时间只允许一个线程执行
②互斥量的接口
[1]互斥量初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
1.如果互斥量是动态分配的,或者需要设定互斥量的属性,那么使用宏初始化的方法就不适用了,
2.调用pthread_mutex_init()之后,互斥量处于没有加锁的状态。
[2]互斥量的销毁
· 使用 PTHREAD_MUTEX_INITIALIZER 初始化的互斥量无须销毁。
· 不要销毁一个已加锁的互斥量,或者是真正配合条件变量使用的互斥量。
· 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
[3]互斥量的加锁和解锁(对于一把锁: 加锁的线程必定是解锁的线程)
int pthread_mutex_lock(pthread_mutex_t *mutex);
(1)调用 pthread_lock()的时候,可能会发生如下事件
· 互斥量处于未锁定的状态,该函数会将互斥量锁定,同时返回成功。
· 发起函数调用时,其他线程已锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,
那么 pthread_lock ()调用会陷入阻塞,等待互斥量解锁。
(2)等待的过程中,如果互斥量持有线程解锁互斥量,可能会发生如下事件:
· 函数调用线程是唯一等待者,获得互斥量,成功返回。
· 函数调用线程不是唯一等待者,但成功获得互斥量,返回。
· 函数调用线程不是唯一等待者,没能获得互斥量,继续阻塞,等待下一轮。
(3) 如果在调用 pthread_lock ()线程时,之前已经调用过 pthread_lock ()且已经持有了互斥量,则根据互斥锁的类型,
存在以下三种可能:
·PTHREAD_MUTEX_NORMAL :这是默认类型的互斥锁,这种情况下会发生死锁,调用线程永久阻塞,线程组的其他线程也无法申请到该互斥量。
·PTHREAD_MUTEX_ERRORCHECK :第二次调用 pthread_mutex_lock 函数时返回 EDEADLK 。
·PTHREAD_MUTEX_RECURSIVE :这种类型的互斥锁内部维护有引用计数,允许锁的持有者再次调用加锁操作。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
如果互斥量已然被锁定,那么当即返回 EBUSY 错误,而不像pthread_mutex_lock ()接口一样陷入阻塞。
int pthread_mutex_timedlock(pthread_mutex_t?*restrict mutex, const struct timespec *restrict abs_timeout);
如果申请互斥量的时候,互斥量已被锁定,那么等待;如果到了 abs_timeout 指定的时间,仍然没有申请到互斥量,那么返回ETIMEOUT 错误
int pthread_mutex_unlock(pthread_mutex_t *mutex);
③临界区的大小
·临界区的范围不能太小,如果太小,可能起不到保护的目的。
·临界区也不能太大,临界区的代码不能并发,如果临界区太大,就无法充分利用多处理器发挥多线程的优势。
·不要将不相干的(特别是可能陷入阻塞的)代码放入临界区内执行。
④互斥量的性能
如果临界区非常小,线程之间对临界区的竞争并不激烈,只会偶尔发生,这种情况下,忙 - 等待的策略要优于互斥量的 “ 让出 CPU ,陷入阻塞,等待唤醒 ” 的策略。
采用忙 - 等待策略的锁为自旋锁。
⑤互斥锁的公平性
1.公平: 如果A在B之前调用 lock()方法,那么A应该先于B获得锁,进入临界区。
2.互斥锁不是一把公平的锁,并没有做到先来先服务。
⑥互斥锁的类型
(1)类型
·PTHREAD_MUTEX_TIMED_NP
·PTHREAD_MUTEX_RECURSIVE
·PTHREAD_MUTEX_ERRORCHECK
·PTHREAD_MUTEX_ADAPTIVE_NP
(2)查询和设置互斥锁的类型
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);
⑦死锁和活锁
(1)死锁
线程1已经成功拿到了互斥量1,正在申请互斥量2,而同时在另一个CPU上,线程2已经拿到了互
斥量2,正在申请互斥量1。彼此占有对方正在申请的互斥量,结局就是谁也没办法拿到想要的互斥
量,于是死锁就发生了。线程1和线程2都会陷入阻塞。
(2)活锁
1.trylock 不行就回退的思想有可能会引发活锁( live lock )。
线程 1 首先申请锁 mutex_a 后,之后尝试申请 mutex_b ,失败以后,释放 mutex_a
进入下一轮循环,同时线程 2 会因为尝试申请 mutex_a 失败,而释放 mutex_b ,
2.如果两个线程恰好一直保持这种节奏,就可能在很长的时间内两者都一次次地擦肩而过。
线程1 线程2
有锁a 有锁b
获锁b 获锁a
trylock trylock // 两个线程都没有获取到对应的锁
释放锁a 释放锁b // 等待下一轮循环
⑧未使用互斥量,访问共享资源的结果
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int g_j = 100;
void *start_routime1(void *arg)
{
while(1) {
printf("%d\n", ++g_j);
sleep(1);
}
pthread_exit(NULL);
}
void *start_routime2(void *arg)
{
while(1) {
printf("%d\n", --g_j);
sleep(1);
}
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t thid1, thid2;
if(pthread_create(&thid1, NULL, start_routime1, NULL))
perror("pthread_create err");
if(pthread_create(&thid2, NULL, start_routime2, NULL))
perror("pthread_create err");
pthread_join(thid1, NULL); // 线程未退出陷入阻塞
pthread_join(thid2, NULL);
return 0;
}
/* 执行结果: 两个线程同时在操作共享资源, 导致后面的结果不可预测
101
100
101
99 // 线程1在执行+1的过程中,线程2执行了2次-1操作
102
101
100
101
*/
⑨使用互斥量,访问共享资源的结果
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
/* 使用锁1和锁2效果相同 */
/* 初始化一个互斥量 */
// 使用宏初始化的互斥量无需销毁, 使用pthread_mutex_init()创建的互斥量需要手动销毁
//static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; // 使用默认属性,互斥量处于解锁状态
static pthread_mutex_t mutex2;
int g_j = 100;
void *start_routime1(void *arg)
{
while(1) {
pthread_mutex_lock(&mutex2);
printf("%d\n", g_j++);
pthread_mutex_unlock(&mutex2);
sleep(1);
}
pthread_exit(NULL);
}
void *start_routime2(void *arg)
{
pthread_mutexattr_t attr;
int type = -1;
int type2 = -1;
// 查询互斥量的类型: 线程锁的默认类型为PTHREAD_MUTEX_NORMAL, 基本上都用这个锁
pthread_mutexattr_gettype(&attr, &type);
printf("type = %d\n", type); //PTHREAD_MUTEX_NORMAL = 0
// 设置线程锁的类型: 为PTHREAD_MUTEX_ERRORCHECK_NP, 只是为了演示设置方法, 一般用默认的锁
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutexattr_gettype(&attr, &type2);
printf("type2 = %d\n", type2); //PTHREAD_MUTEX_ERRORCHECK_NP = 2
while(1) {
pthread_mutex_lock(&mutex2);
printf("%d\n", g_j--);
pthread_mutex_unlock(&mutex2);
sleep(1);
}
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_t thid1, thid2;
pthread_mutex_init(&mutex2, NULL); //使用默认属性,互斥量处于解锁状态
if(pthread_create(&thid1, NULL, start_routime1, NULL))
perror("pthread_create err");
if(pthread_create(&thid2, NULL, start_routime2, NULL))
perror("pthread_create err");
pthread_join(thid1, NULL); // 线程未退出陷入阻塞
pthread_join(thid2, NULL);
pthread_mutex_destroy(&mutex2);
return 0;
}
/* 运行结果 : // 一次计算只会+1或者-1, 保证了同一时间只有一个线程在操作共享资源
book@gui_hua_shu:$ ./a.out
101
type = 0
type2 = 2
100
99
100
99
100
99
100
101
*/