1.进程线程间的互斥相关背景概念
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态要么完成,要么未完成
2.引入
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void* route(void* arg)
{
char* id = (char*)arg;
while (1) {
if (ticket > 0) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
}
else {
break;
}
}
}
int main(void)
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
为什么可能无法获得争取结果?
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
- --ticket 操作本身就不是一个原子操作
要避免ticket为负数,我们需要对ticket进行保护,也就是加上互斥锁,让各个线程在同一时间只能有一个对ticket进行访问。
3.互斥量的接口
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER//全局锁初始化,会自动销毁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);//局部锁初始化
//参数:
//mutex:要初始化的互斥量
//attr:一般为NULL即可
int pthread_mutex_destroy(pthread_mutex_t *mutex);//局部锁销毁
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
下面我们来改进上面的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
int ticket = 100;
pthread_mutex_t mutex;
void* route(void* arg)
{
char* id = (char*)arg;
while (1) {
pthread_mutex_lock(&mutex);//2.加锁
if (ticket > 0) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
pthread_mutex_unlock(&mutex);//3.解锁
}
else {
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main(void)
{
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);//1.初始化
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
pthread_mutex_destroy(&mutex);//4.销毁锁
}
这样我们通过互斥锁对临界资源ticket进行保护,确保了同一时间只有一个进程对ticket进行访问,保证票数正常。
4.互斥锁的实现
伪代码如下:
al代表cpu的一个寄存器,lock操作先是把寄存器al置为0,然后再交换互斥量和al的值 ,这里都是一句汇编完成。这样mutex初始为1,在之后的任一时刻,在al寄存器和mutex中,只会有一个为1,当al为1,就代表抢到了锁。同时al寄存器的内容属于硬件上下文,为线程私有,当线程切换时,线程会对自己的硬件上下文进行保存,这就保证在任何线程的al寄存器和mutex的值中只会有一个1。