前言
大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁
主要内容含:
欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!
- YY的《C++》专栏
- YY的《C++11》专栏
- YY的《Linux》专栏
- YY的《数据结构》专栏
- YY的《C语言基础》专栏
- YY的《初学者易错点》专栏
- YY的《小小知识点》专栏
- YY的《单片机期末速过》专栏
- YY的《C++期末速过》专栏
- YY的《单片机》专栏
- YY的《STM32》专栏
- YY的《数据库》专栏
- YY的《数据库原理》专栏
目录
- 一.抢票问题展示——"票数变成负数"
- 1.问题展示:
- 2.“ticket--”执行的过程是''非原子的'':多个线程会进入该代码段
- 二.互斥&临界区&临界资源
- 三.互斥量(锁)
- 1.互斥量所需的头文件
- 2.互斥量的初始化(动态&静态)
- 3.互斥量的销毁
- 4.互斥量的加锁&解锁
- 四.<互斥量>解决<抢票问题>
一.抢票问题展示——“票数变成负数”
1.问题展示:
- 下面代码所示
- 我们会发现票数逐渐减少,最后甚至 减成了负数
- 但是明明我们route函数里面设置的
if ( ticket > 0 )
后面才会ticket--
,这是为什么? - 原因: ticket–的操作是 非原子性的,会被调度机制打断, 有多个线程会进入该代码段
- 关于原子性,下面有详细分析
// 操作共享变量会有问题的售票系统代码
#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);
}--一次执行结果:
thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1//减成负数
thread 3 sells ticket:-2
2.“ticket–”执行的过程是’‘非原子的’':多个线程会进入该代码段
- 原子性:
- 原子性: 不会被任何调度机制打断的操作
- 该操作只有两态,要么 完成 ,要么 未完成
-- 操作
并不是原子操作,而是对应 三条汇编指令:
- load :将共享变量ticket从内存加载到寄存器中
- update : 更新寄存器里面的值,执行-1操作
- store :将新值,从寄存器写回共享变量ticket的内存地址
- 票数变成负数原因分析 / ticket–分析:
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中, 可能有很多个线程会进入该代码段
- –ticket 操作本身就不是一个原子操作
二.互斥&临界区&临界资源
通过上述问题,我们明白:
-
代码 必须要有互斥行为 : 当代码进入临界区执行时,不允许其他线程进入该临界区。
-
互斥:在任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源,通常对临界资源起保护作用
-
能实现该 互斥行为 的,也就是我们下面用到的 锁 ,即 互斥量
-
如果多个线程同时要求执行临界区的代码, 任何一个时刻, 也只允许一个线程正在访问共享资源
-
我们把我们进程中访问临界资源的代码片段,称为 临界区
-
对应上文提到抢票问题,我们也明确了共享区,以及该加锁解锁的位置,如下图所示:
三.互斥量(锁)
1.互斥量所需的头文件
- 线程库中有互斥锁
#include <pthread.h> #include <stdio.h>
2.互斥量的初始化(动态&静态)
初始化互斥量有两种方法:静态初始化和动态初始化
- 方法1,静态初始化:
- 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁!!!
静态初始化的互斥量不需要显式调用pthread_mutex_destroy
函数进行销毁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#include <pthread.h> #include <stdio.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* thread_func(void* arg) { pthread_mutex_lock(&mutex); // 访问共享资源 // ... 执行一些操作 ... pthread_mutex_unlock(&mutex); // 释放互斥量 return NULL; } int main() { pthread_t thread1, thread2; // 创建两个线程 pthread_create(&thread1, NULL, thread_func, NULL); pthread_create(&thread2, NULL, thread_func, NULL); // 等待两个线程结束 pthread_join(thread1, NULL); pthread_join(thread2, NULL); // 注意:静态初始化的互斥量不需要显式销毁 return 0; }
- 方法2,动态初始化
- 动态初始化的互斥量在使用完毕后需要显式调用
pthread_mutex_destroy
函数进行销毁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:mutex:要初始化的互斥量attr:指向互斥量属性对象的指针。如果传递NULL,则使用默认的互斥量属性(通常是非递归、非错误检查的)
#include <pthread.h> #include <stdio.h> // 互斥量 pthread_mutex_t mutex; void* thread_func(void* arg) { pthread_mutex_lock(&mutex); // 访问共享资源 // ... 执行一些操作 ... pthread_mutex_unlock(&mutex); // 释放互斥量 return NULL; } int main() { pthread_t thread1, thread2; // 动态初始化互斥量 pthread_mutex_init(&mutex, NULL); // 创建两个线程 pthread_create(&thread1, NULL, thread_func, NULL); pthread_create(&thread2, NULL, thread_func, NULL); // 等待两个线程结束 pthread_join(thread1, NULL); pthread_join(thread2, NULL); // 注意:动态初始化的互斥量需要显式销毁 // 销毁互斥量 pthread_mutex_destroy(&mutex); return 0; }
3.互斥量的销毁
- 销毁互斥量要注意:
- 使用
PTHREAD_ MUTEX_ INITIALIZER
初始化的互斥量不需要销毁 - 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
- 销毁互斥量语法:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
4.互斥量的加锁&解锁
- 互斥量的加锁&解锁语法:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
四.<互斥量>解决<抢票问题>
- 现在明确了 共享区与要加锁的位置 ,也清楚了 锁(互斥量)的语法
- 改进原来的售票系统:
#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);//进入共享区前上锁if ( ticket > 0 ) {usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&mutex);//退出共享区时解锁} else {pthread_mutex_unlock(&mutex);//退出共享区时解锁break;}}
}int main( void )
{pthread_t t1, t2, t3, t4;pthread_mutex_init(&mutex, NULL);//锁的初始化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);//销毁锁
}