当前位置: 首页 > news >正文

【linux学习指南】线程同步与互斥

请添加图片描述

文章目录

  • 📝线程互斥
  • 🌠 库函数strncpy
    • 🌉进程线程间的互斥相关背景概念
    • 🌉互斥量mutex
  • 🌠线程同步
    • 🌉条件变量
    • 🌉同步概念与竞态条件
    • 🌉 条件变量函数
  • 🚩总结


📝线程互斥

🌠 库函数strncpy

🌉进程线程间的互斥相关背景概念

  • 临界资源:多线程执⾏流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起保护作⽤
  • 原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

🌉互斥量mutex

  • ⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程⽆法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来⼀些问题。

Makefile文件

bin=ticket
cc=g++
src=$(wildcard *.cc)
obj=$(src:.cc=.o)$(bin):$(obj)$(cc) -o $@ $^ -lpthread
%.o:%.cc@echo "Comiling $< to $@"$(cc) -c $< -std=c++17.PHONY:clean
clean:rm -f $(bin) $(obj).PHONY:test
test:echo $(src)echo $(obj)

代码:

#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>int ticket = 100;void* routine(void* args)
{char *id = (char*)args;// std::string id = static_cast<const char*>(args);while(1){if(ticket > 0){usleep(10000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}int main(void)
{pthread_t t1, t2 , t3, t4;pthread_create(&t1, nullptr, routine, (void*)"thread 1");pthread_create(&t2, nullptr, routine, (void*)"thread 2");pthread_create(&t3, nullptr, routine, (void*)"thread 3");pthread_create(&t4, nullptr, routine, (void*)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • if语句判断条件为真以后,代码可以并发的切换到其他进程
  • usleep这个模拟夜漫长业务的过程这个漫长的业务过程中,可能有多个线程会进入该代码段
  • --ticket操作本身就不是一个原子操作

取出ticket–部分的汇编代码

objdump -d a.out > test.objdump152   40064b:   8b 05 e3 04 20 00     mov       0x2004e3(%rip),%eax 
600b34 <ticket>153   400651:   83 e8 01                	 sub        $0x1,%eax
154   400654:   89 05 da 04 20 00       mov		%eax,0x2004da(%rip)  
600b34 <ticket>

--操作并不是原子操作而是对应三条汇编指令:

  • load将共享变量体的从内存加载到寄存器
  • update更新寄存器里面的只执行复议操作
  • store:将新值从寄存器写回共享变量ticket的内存地址

要解决以上问题需要做到三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他进程进入该临界区
  • 如果多个线程同时要求进入临界区的代码,并且临界区没有线程在执行,那么只能一个线程进入该临界区
  • 如果现场不在临界区中执行,那么该现场就不能阻止其他进程进入临界区

要做到这三点,本身是上就是需要一把锁,linux上提供这把锁叫互斥量
在这里插入图片描述
互斥量的接口
初始化互斥量的两种方法

  • 方法1:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER 
  • ⽅法2,动态分配:
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    参数:
    mutex:要初始化的互斥量
    attr:这是一个指向 pthread_mutexattr_t 类型对象的指针,该类型用于定义互斥锁的属性。如果将其设置为 NULL

销毁互斥量
销毁互斥量需要注意:

  • 使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁⼀个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回o,失败返回错误号

用pthread_ lock时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

改进上面的售票系统:

#include <stdio.h>
#include <string>
#include <string.h>
#include <pthread.h>
#include <unistd.h>int ticket = 100;
pthread_mutex_t mutex;void* routine(void* args)
{char *id = (char*)args;// std::string id = static_cast<const char*>(args);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;}}return nullptr;
}int main(void)
{pthread_t t1, t2 , t3, t4;pthread_create(&t1, nullptr, routine, (void*)"thread 1");pthread_create(&t2, nullptr, routine, (void*)"thread 2");pthread_create(&t3, nullptr, routine, (void*)"thread 3");pthread_create(&t4, nullptr, routine, (void*)"thread 4");pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);pthread_join(t4, nullptr);pthread_mutex_destroy(&mutex);return 0;
}

在这里插入图片描述
在这里插入图片描述

RAII风格的互斥锁,C++11也有,比如:
std : : mutex mtx;
std : : lock_guard guard ( mtx ) ;

🌠线程同步

🌉条件变量

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

🌉同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题,叫做同步
  • 竞态条件:因为时序问题,⽽导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也
    不难理解

🌉 条件变量函数

初始化

int pthread_cond_init(pthread_cond_t *restrict cond,   const pthread_condattr_t*restrict attr);

参数:
cond:要初始化的条件变量
attr: NULL

销毁:

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满⾜

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrictmutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

简单案例:

  • 我们先使用PTHREAD_COND/MUTEX_INITIALIZER进行测试,对其他细节暂不追究
  • 然后将接口更改成为使用pthread_cond_init/pthread_cond_destroy的方式,方便后续进行封装
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* active(void* args)
{std::string name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);std::cout<< name << "活动..." << std::endl;pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t t1, t2;pthread_create(&t1, nullptr, active, (void*)"thread -1");pthread_create(&t2, nullptr, active, (void*)"thread -2");sleep(3);while(true){//对比测试pthread_cond_signal(&cond);//唤醒一个线程// pthread_cond_broadcast(&cond);//唤醒所有线程sleep(1);}pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}

在这里插入图片描述


🚩总结

请添加图片描述

http://www.lryc.cn/news/536135.html

相关文章:

  • JavaScript函数与方法详解
  • 【论文笔记】ZeroGS:扩展Spann3R+GS+pose估计
  • AtCoder - arc058_d Iroha Loves Strings解答与注意事项
  • 企业使用统一终端管理(UEM)工具提高端点安全性
  • Leetcode 算法题 9 回文数
  • 设计模式Python版 命令模式(上)
  • C语言之循环结构:直到型循环
  • 细说STM32F407单片机RTC的备份寄存器原理及使用方法
  • MATLAB计算反映热需求和能源消耗的度数日指标(HDD+CDD)(全代码)
  • J6 X8B/X3C切换HDR各帧图像
  • 09-轮转数组
  • 用vue3写一个好看的wiki前端页面
  • 瑞芯微烧写工具
  • 说下JVM中一次完整的GC流程?
  • Open FPV VTX开源之OSD使用分类
  • 智慧农业-虫害及生长预测
  • Python 识别图片和扫描PDF中的文字
  • C语言如何知道当前系统中的编译器数据类型的大小是多少?
  • gitlab Webhook 配置jenkins时“触发远程构建 (例如,使用脚本)”报错
  • Mysql中使用sql语句生成雪花算法Id
  • /etc/profile vs ~/.bashrc:如何正确使用?
  • SpringBoot实战:高效获取视频资源
  • Flutter_学习记录_数据更新的学习
  • c++ 多线程知识汇总
  • day09_实时类标签/指标
  • 【前端开发学习笔记16】Vue_9
  • Bash 中的运算方式
  • 2025年3月营销灵感日历
  • MySQL的innoDB引擎
  • HCIA项目实践---OSPF的知识和原理总结