前言
大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁
主要内容含:
欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!
目录
一.抢票问题展示——“票数变成负数”
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.互斥量的初始化(动态&静态)
初始化互斥量有两种方法:静态初始化和动态初始化
- 方法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;
}
2.互斥量的销毁
- 销毁互斥量要注意:
- 使用
PTHREAD_ MUTEX_ INITIALIZER
初始化的互斥量不需要销毁 - 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
- 销毁互斥量语法:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
3.互斥量的加锁&解锁
- 互斥量的加锁&解锁语法:
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);//销毁锁
}
标签:NULL,thread,抢票,互斥,mutex,pthread,ticket,多线程
From: https://blog.csdn.net/YYDsis/article/details/142934452