目录
只有认知的突破 💫才能带来真正的成长 💫编程技术的学习 💫没有捷径 💫一起加油💫

🍁感谢各位的观看 🍁欢迎大家留言 🍁咱们一起加油 🍁努力成为更好的自己🍁
线程同步与互斥
抢票的问题
用多个线程模拟多个用户抢票,如果对于公共资源不加保护,就会出现严重的问题。如下所示的代码。
cpp
// 操作共享变量会有问题的售票系统代码
#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; //创建4个线程
pthread_create(&t1, NULL, route, (void *)"thread 1");
pthread_create(&t2, NULL, route, (void *)"thread 2");
pthread_create(&t3, NULL, route, (void *)"thread 3");
pthread_create(&t4, NULL, route, (void *)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
return 0;
}

**现象:**票被抢到负数!!!。这就是没有对共享资源的保护!!!
相关概念
-
共享资源 。
-
临界资源:临界区对应的资源,叫做临界资源。
-
临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
-
非临界区:非访问临界资源的代码,叫做非临界区。
-
互斥:任何时刻,互斥保证有且只有⼀个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
-
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。
如下图所示。

互斥
互斥锁
互斥锁可以解决上面抢票的问题。
过程:每个线程在抢票的时候,先申请一把锁,谁有锁,才可以抢票,抢完票后再把锁释放,下一个线程再申请锁......。反复这个过程,即可避免抢票的问题。
如下图所示。

锁的创建
- 静态锁的创建------创建一个全局锁(变量)。
代码:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
cpp
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int main()
{
//........//
return 0;
}
-
动态锁的创建------创建一个局部锁(变量)。
-
函数:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr),动态锁的创建,需要使用该函数进行初始化。 -
**参数:**mutex:要初始化的互斥锁。attr:NULL。
-
cpp
int main()
{
pthread_mutex_t mutex; //创建锁
pthread_mutex_init(&mutex, NULL); //锁的初始化
//........//
return 0;
}
上锁
函数:int pthread_mutex_lock(pthread_mutex_t *mutex)。
释放锁
函数:int pthread_mutex_unlock(pthread_mutex_t *mutex)。
销毁锁
函数:int pthread_mutex_destroy(pthread_mutex_t *mutex)。
解决抢票的问题
票就是临界资源,对临界资源加锁,即可解决问题,如下所示的代码。
cpp
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 10; //总票数
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *route(void *arg)
{
char *id = (char *)arg;
while (1)
{
pthread_mutex_lock(&mutex); //加锁
if (ticket > 0)
{
sleep(1);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
pthread_mutex_unlock(&mutex);//释放锁
}
else
{
printf("%s sells ticket:%d\n", id, ticket);
break;
}
}
//避免抢到票为0,锁未释放
pthread_mutex_unlock(&mutex);//释放锁
return (void*)0;
}
int main(void)
{
pthread_t t1, t2, t3, t4; //创建4个线程
pthread_create(&t1, NULL, route, (void *)"thread 1");
pthread_create(&t2, NULL, route, (void *)"thread 2");
pthread_create(&t3, NULL, route, (void *)"thread 3");
pthread_create(&t4, NULL, route, (void *)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
//等待完所有的线程后,进行锁的销毁
pthread_mutex_destroy(&mutex);
return 0;
}

注意 :虽然保证了资源的安全,但是出现了一个线程独霸一方的局面,其它线程无法得到有效的资源,这就造成了饥饿问题,为了解决这个问题就需要条件变量和互斥锁配合了。
死锁
死锁的原因
以两个线程为例子,线程A和线程B。线程A持有线程B的锁,线程B持有线程A的锁。线程A访问资源需要线程B持有的锁,而线程B访问资源需要线程A持有的锁,双方都需要对方的锁,双方不释放,就这样僵持着,就形成了死锁。如下所示。

形成死锁的四个条件
- 互斥条件
资源具有排他性,同一时间只能被一个进程占用,其他进程想要使用该资源,必须等待当前占用进程释放。
- 请求和保持条件(占有且等待)
进程已经占有了至少一个资源 ,又提出了新的资源请求,而新资源被其他进程占用,该进程会保持已占有的资源,同时等待新资源,且在等待期间不释放已占资源。
- 不剥夺条件(非抢占条件)
进程所占有的资源不能被强制剥夺 ,只能由进程主动释放,其他进程无法强行抢占正在被占用的资源。
- 循环等待条件
系统中存在一组进程 ,形成环形的资源等待链:进程 1 等待进程 2 占有的资源,进程 2 等待进程 3 占有的资源......,最后一个进程等待进程 1 占有的资源,每个进程都在链中等待下一个进程释放资源。
解决死锁
方法:破坏形成死锁四个条件的任意一个条件,就会破坏死锁。