临界区与临界资源
定义
临界资源(Critical Resource): 一次只允许一个执行流访问的公共资源称为临界资源.比如在生活中,一个厕所隔间或者公交座位就是临界资源(常规情况下).
临界区(Critical Section): 对公共资源进行访问的程序片段,也就是不加访问控制,会有安全问题的代码段.
但在多线程的场景下,可能会出现多个执行流并发访问同一个公共资源的情况,这时候,我们就要通过各种方式,保证他是临界资源.
案例
如下这段代码下,全局变量ticket 被多线程共享,此时它时公共资源,但各线程并发对它读写,他就不是临界资源,所以ticket会被抢成负数.而if语句内对这个公共资源进行判断和自减,就是临界区:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
// 全局变量
int ticket = 10000;
void *thread_routine(void *arg)
{
int id = reinterpret_cast<long int>(arg);
//多个进程同时读写num
while (1)
{
//临界区
if (ticket>0)
{
usleep(999);
cout << "thread_" << id << " get ticket: NO. " << ticket-- << endl;
}
else
{
cout << "thread_" << id << " no ticket left..." << endl;
usleep(123);
break;
}
}
}
int main()
{
pthread_t threads[4];
for (int i = 0; i < 4; i++)
pthread_create(threads + i, nullptr, thread_routine, reinterpret_cast<void *>(i));
for (int i = 0; i < 4; i++)
pthread_join(threads[i], nullptr);
return 0;
}
原子性
要保证案例中的代码不出问题,就需要保证对公共资源的访问是原子的.
定义: 原子性指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。
意思是如果每个线程(执行流)对某公共资源的访问操作:
要么开始就不被其他线程打断,要么没开始访问。
这时我们就称,对这个资源的操作具有原子性.
案例中,各个线程都在这个资源正在被其他线程操作时就又可以访问这个公共资源!
故不符合原子性,才导致问题的出现.
让对公共资源具有原子性的操作有很多,如线程同步,互斥锁,信号量等
这里演示使用互斥锁,解决解决案例问题:
互斥锁的原理就是进入临界区必须要先申请锁,只有当资源处于状态"没开始被访问"才能申请到.否则就阻塞等待其他线程访问完毕.
线程在退出临界区时解锁(也就是归还锁),这样其他阻塞的线程才能申请到锁,
锁存在于线程的上下文,就算对资源操作到一半被切走,其他线程也申请不到锁,无法进入临界区!
这样一来,我们就保证了对上了锁的公共资源的操作具有原子性,一个时刻只能被一个执行流它访问,保证了它是临界资源!
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
// 临界资源
int ticket = 10000;
//互斥锁
pthread_mutex_t ticket_mutex = PTHREAD_MUTEX_INITIALIZER;
void *thread_routine(void *arg)
{
int id = reinterpret_cast<long int>(arg);
//多个进程同时读写num
while (1)
{
//临界区
//加锁
pthread_mutex_lock(&ticket_mutex);
if (ticket > 0)
{
cout << "thread_" << id << " get ticket: NO. " << ticket-- << endl;
//解锁
pthread_mutex_unlock(&ticket_mutex);
usleep(999);
}
else
{
//解锁 确保每个路径都能正确解锁
pthread_mutex_unlock(&ticket_mutex);
cout << "thread_" << id << " no ticket left..." << endl;
usleep(123);
break;
}
}
}
int main()
{
pthread_t threads[4];
for (int i = 0; i < 4; i++)
pthread_create(threads + i, nullptr, thread_routine, reinterpret_cast<void *>(i));
for (int i = 0; i < 4; i++)
pthread_join(threads[i], nullptr);
return 0;
}