一、进程线程间互斥背景相关概念:
共享资源
临界资源:多线程执行流被保护的共享的资源就叫做临界资源。
临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起 保护作用。
多个线程向公共资源写入数据,会发生数据不同步的情况,所以需要进程线程同步。
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成, 要么未完成。
互斥量mutex:
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量 归属单个线程,其他线程无法获得这种变量。
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完 成线程之间的交互。
多个线程并发的操作共享变量,会带来一些问题:
比如数据不一致问题:
模拟抢票:
cpp
int ticket = 100;
void *routine(void *args)
{
char *id = (char *)args;
while(1)
{
if(ticket>0)
{
usleep(1000);//模拟抢票时间
printf("%s sells ticket:%d\n", id, ticket);//抢到票
ticket--;//票数--
}
else
break;
}
return nullptr;
}
int main()
{
pthread_t t1,t2,t3,t4;
pthread_create(&t1, nullptr, routine, (void *)"thread-l");
pthread_create(&t2, nullptr, routine, (void *)"thread-l");
pthread_create(&t3, nullptr, routine, (void *)"thread-l");
pthread_create(&t4, nullptr, routine, (void *)"thread-l");
pthread_join(t1,nullptr);
pthread_join(t2, nullptr);
pthread_join(t3, nullptr);
pthread_join(t4, nullptr);
std::cout << "新线程被分离" << std::endl;
return 0;
}

票竟然变成负数,不符合实际情况。
ticket载入CPU的寄存器当中,CPU再去读取寄存器做计算。
减完之后的值再写回内存。
执行第二步时,线程切换,线程A保存临时数据,CPU内的数据可以被覆盖了,线程B去做--,减到一半又切换,变成A的线程,线程A的值写入内存。
全局资源没有保护可能会发生并发问题。
二、解决这个问题(锁)
1.代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
2.如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许⼀个线程 进入该临界区。
3.如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
互斥锁:
竞争申请锁,线程就要先看到锁,成功继续向后运行,访问临界资源,失败,阻塞挂起申请执行流。
锁提供的能力:执行临界区的代码由并行转为串行。
cpp
int ticket = 100;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 对锁进行初始化
void *routine(void *args)
{
char *id = (char *)args;
while (1)
{
pthread_mutex_lock(&lock); // 加锁
if (ticket > 0)
{
usleep(1000); // 模拟抢票时间
printf("%s sells ticket:%d\n", id, ticket); // 抢到票
ticket--; // 票数--
pthread_mutex_unlock(&lock); // 解锁
// if里面就是临界区
}
else
break;
}
return nullptr;
}
int main()
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, nullptr, routine, (void *)"thread-l");
pthread_create(&t2, nullptr, routine, (void *)"thread-l");
pthread_create(&t3, nullptr, routine, (void *)"thread-l");
pthread_create(&t4, nullptr, routine, (void *)"thread-l");
pthread_join(t1, nullptr);
pthread_join(t2, nullptr);
pthread_join(t3, nullptr);
pthread_join(t4, nullptr);
return 0;
}

三、理解锁:
对临界资源保护,本质是用锁对临界区的代码进行保护
如果有线程不遵守?
加锁之后是否允许线程切换?允许。但是持有锁被切换,其他线程得等该线程执行完代码,释放锁,其他线程才能展开对锁的竞争,执行期间不会被打扰。
寄存器硬件只有一套,但数据可以有多份,当前执行流的上下文,把一个变量的内容交换到CPU寄存器内部本质是把数据获取到当前执行流的上下文。
谁有mutex的1谁就持有锁,申请锁,寄存器al先置0,然后mutex和al的值交换,al的值是1,就申请锁成功,如果线程切换,1会被带走。
线程同步:解锁后不能立即申请第二次锁,外面的线程进行排队,退出的线程必须跑到队尾去申请锁。在保证自习室安全的前提下,让所有的执行流访问临界资源按照一定的顺序进行访问。这是线程同步。