【 linux 】线程互斥与同步

目录

[1. 线程本质](#1. 线程本质)

[2. 线程互斥](#2. 线程互斥)

[2.1 信号量](#2.1 信号量)

[2.2 认识锁的接口](#2.2 认识锁的接口)

[2.3 理解锁](#2.3 理解锁)

3.线程同步

[3.1 条件变量](#3.1 条件变量)


1. 线程本质

在用户层,封装轻量级进程,形成原生线程库pthread库,是动态库也是ELF库,动态库需要加载到内存。线程的概念是在库中定义的,那在库中就一定有多个创建好的线程,库当然也要管理线程:先描述,再组织

bash 复制代码
struct tcb <-- pthread_create
{
    线程状态
    线程id
    线程独立的栈结构
    线程栈大小
    ...
}

2. 线程互斥

先区分几组概念

临界资源:多线程执行流被保护的共享的资源就叫做临界资源

临界区;每个线程内部,访问临界资源的代码,叫做临界区

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

2.1 信号量

本质是一个计数器,用来表明临界资源中资源的数量。可以用电影院买票来理解,想访问临界资源都得先买票,进程访问临界资源,先申请信号量,本质是对资源的预定机制。信号量本身就是共享资源,信号量只有0和1两态的信号量,叫做二元信号量,也就是互斥

数据不一致现象

bash 复制代码
int ticket=100;

void* Routine(void* args)
{
 char* id=(char*)args;
 while(1)
{
    if(ticket>0)
    {

        usleep(1000);
        printf("%s sells ticket:%d",s,id);
        ticket--;
    }
    else break;
}

    return nullptr;
}

可以看到票数减到负数

ticket全局资源没有加保护,就会产生并发问题,线程安全问题。需要加锁来解决

2.2 认识锁的接口

全局:pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER

局部:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

申请锁,释放锁:int pthread_mutex_lock(pthread_mutex_t*mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

竞争申请锁,多线程都得先看到锁,锁本身就是临界资源!!申请锁的过程,必须是原子的

失败:阻塞挂起申请执行流

成功,继续向后运行,访问临界区代码,访问临界资源

锁提供的能力的本质:执行临界区代码由并行转换成为串行

对临界资源的保护:本质就是用锁,对临界区代码进行保护

可以想象成自习室

加锁之后,临界区代码是允许线程切换的。但是:因为我当前线程,并没有释放锁,我是持有锁被切换的,即便我不在!其他线程也得等我回来执行完代码,释放锁,其他线程才能展开锁的竞争,进入临界区

2.3 理解锁

1.硬件级实现:关闭时钟中断

2.软件级实现:为了实现互斥锁操作.大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单无的数据相交换

如果把一个变量的内容,交换到CPU寄存器内部,本质是:把该变量的内容,获取到当前执行流的硬件上下文中,当前CPU寄存器的硬件上下文(其实就是各个寄存器的内容)属于:进程/线程私有的!!!!!!我们用swap,exchange将内存中的变量,交换到CPU的寄存器中:本质是当前线程/进程,在获取锁,因为是交换,不是拷贝!!!,所以1只有一份!!,所以,谁申请到,谁持有锁!!

3.线程同步

锁的技术也带来了新的问题,如果一个线程高频的申请锁,没有做有效的动作,其他线程又申请不到就会产生线程饥饿问题。为了解决线程饥饿就需要制定新的方案:

1.不能立即申请第二次

2.外边的人,进行排队,退出的人,必须跑到队列的尾部,进行二次申请

在保证自习室安全的前提下,让所有的执行流,访问临界资源按照一定的顺序进行访问资源!!:

线程同步!!

3.1 条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量

举个放苹果的例子:

接口

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t)*cond);

int pthread cond init(pthread cond t *restrict cond, const pthread condattr t *restrict attr);

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t*restrict mutex)

在指定的条件变量下等待

int pthread_cond_broadcast(pthread_cond_t *cond);唤醒在指定条件变量下等待的所有的线程

int pthread_cond_signal(pthread_cond_t *cond);唤醒在该条件变量下等待的一个线程

点个关注不迷路

相关推荐
学长毕业设计1 小时前
基于SpringBoot的校园二手物品交易系统(源码+文档+讲解视频)
java·spring boot·后端
可爱系程序猿1 小时前
libcrypto-3-x64.dll 加载失败排查:OpenSSL 3 版本、PATH 顺序与 x64 依赖如何对应
linux·运维·服务器·程序人生·电脑
pingglala1 小时前
C++线程安全队列对比
开发语言·c++
观无1 小时前
若依AOP
java
zbyyd1 小时前
Linux 网络编程:IO 多路复用
linux·运维·c语言·网络
fengyehongWorld1 小时前
Linux 域名申请与DNS解析
linux·运维·服务器
是2的10次方啊1 小时前
日志不是越多越好:开发优化、常见坑与线上排查命令
java
ofoxcoding1 小时前
Claude Fable 5.1 API 接入指南:模型标识、五级定价策略、effort 参数与缓存 TTL 配置详解
java·spring·缓存·ai
晊晌_h1 小时前
嵌入式从0到精通——Linux 网络通信|TCP、HTTP 网络编程
linux·c语言·网络