线程同步的实现

2023-05-16

互斥锁

        互斥锁的使用就是当有线程访问进程空间中的公共资源时,线程执行“加锁”操作(将资源锁起来),阻止其它线程的访问。访问完成后,该线程(谁锁上的必须由谁来解锁)负责完成“解锁”操作,将资源让给其它线程。当有多个线程同时需要访问同一个公共资源时,谁先上锁成功,这公共资源就归谁使用,只有等他用完了,释放锁了,其他线程才能用

互斥锁的使用流程

(1)定义一个互斥锁

        在<pthread.h>头文件中,有一个专门用来定义互斥锁变量的结构体,叫pthread_mutex_t,我们直接拿来用就是了,使用方法如下

pthread_mutex_t myMutex;

(2)初始化互斥锁

        互斥锁的初始化方法有两种,一种是使用特定的宏,一种是使用专门的初始化函数,一般没啥特殊要求我们就用第一种宏就行,对于调用 malloc() 函数分配动态内存的互斥锁,只能使用第二种方式。

使用宏的方法

pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;

使用函数的方法

pthread_mutex_t myMutex;
pthread_mutex_init(&myMutex , NULL);

关于pthread_mutex_init() 函数,他的原型是这样的

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

关于他的参数,mutex 参数表示要初始化的互斥锁;attr 参数用于自定义新建互斥锁的属性,当attr 的值为 NULL 时表示以默认属性创建互斥锁,我们一般也就使用NULL。

(3)加锁

int pthread_mutex_lock(pthread_mutex_t* mutex);  

//或者用下面这个

int pthread_mutex_trylock(pthread_mutex_t* mutex); 

有关两者的区别,具体如下

pthread_mutex_lock() 函数会使线程进入等待(阻塞)状态,直至互斥锁得到释放;

pthread_mutex_trylock() 函数不会阻塞线程,直接返回非零数(表示加锁失败)。

(4)解锁

int pthread_mutex_unlock(pthread_mutex_t* mutex); 

说到最后,用一个实际案例来实验一下,模拟的是网上买票的工作流程。创建了四个子线程,去模拟不同的买票窗口去负责卖票。

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
int ticket_sum = 30;//这表示票的总量

//创建互斥锁变量并初始化
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;

//模拟售票员卖票
void *sell_ticket(void *arg) 
{
    //输出当前线程的ID
    printf("id = %u\n", pthread_self());
    int i;
    int islock = 0;
    for (i = 0; i < 10; i++)
    {
        //当前线程“加锁”
        islock = pthread_mutex_lock(&myMutex);
        //如果“加锁”成功,执行如下代码
        if (islock == 0)
        {
            //如果票数 >0 ,开始卖票
            if (ticket_sum > 0)
            {
                sleep(1);
				printf("%u sell the NO.%d ticket\n",pthread_self(),30-ticket_sum+1);
                ticket_sum--;
            }
           	pthread_mutex_unlock(&myMutex);
        }
       
    }
    return 0;
}
int main() 
{
    int flag;
    int i;
    //创建4个线程
    pthread_t tids[4];
    for (i = 0; i < 4; i++)
    {
        flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
        if (flag != 0) 
        {
            printf("fail\n");
            return 0;
        }
    }
    sleep(30);   //等待4个子线程完成
    for (i = 0; i < 4; i++)
    {
        flag = pthread_join(tids[i], NULL);
        if (flag != 0) 
        {
            printf("tid=%d wait fail !", tids[i]);
            return 0;
        }
    }
    return 0;
}

        程序中pthread_self()这个其实是这样子的,我直接man出来,大家看一下,就是用来返回,调用线程的id的。

        关于这个程序案例,写完后,我自己其实运行了几次,之前我将票的总数量设置成10,发现从头到尾只有一个线程在被调用,我以为是我的程序有问题,在网上查阅了一下资料发现,是我们当前的电脑机子性能太好所致,当操作数量少的情况下的多线程,电脑自己其实就使用一个线程就给我们安排了,于是我加大了数量,这才发生点变化出来

信号量

        互斥锁的状态值只有开锁和解锁两个方面,信号量则不同,他可以根据实际场景的不同设置自己想要的转态值,可以说是更加灵活吧。

        信号量可以分成是二进制信号量和计数信号量,二进制信号量就可以用来替代互斥锁,就是只将信号量的初始值设置成1,然后只能0,1间变换,可以理解成每次只允许一个线程同时访问一片公共资源,而计数信号量就是将信号量的初始值设置的多一点,大于1的那种,意味着每次可以同时接待多个线程来访问公共资源。

下面来看看信号量这东西的用法,具体咋用

sem_t  mySem;        //先定义一个信号量

//信号量的初始化

int sem_init(sem_t *sem, int pshared, unsigned int value);

各个参数的含义分别为:

1、参数sem:表示要初始化的目标信号量;

2、参数pshared:表示该信号量是否可以和其他进程共享,pshared 值为 0 时表示不共享,值为 1 时表示共享;

3、参数value:设置信号量的初始值

对于初始化好了的信号量,我们可以借助 <semaphore.h> 头文件提供的一些函数操作

入参都是要操作的信号量

//将信号量的值“加 1”

int sem_post(sem_t* sem);        

//对信号量做“减 1”操作,当信号量的值为 0 时,阻塞当前线程等待

int sem_wait(sem_t* sem); 

//当信号量的值为 0 时,sem_trywait() 函数并不会阻塞当前线程,而是立即返回 -1;

int sem_trywait(sem_t* sem);

//手动销毁信号量

int sem_destroy(sem_t* sem);

当一个线程要用公共资源的时候就先,sem_wait,等待处理完后再sem_post,把位置让出来,就好比银行里的多个服务窗口,你坐那办理业务的时候就需要把可用位置减1,也就是sem_wait,等你办理完后就需要把位置让出来,也就是sem_post。

条件变量

条件变量是线程同步的另一种手段,主要逻辑就是等待和唤醒。条件不满足时,线程等待;条件满足时,线程被(其他线程)唤醒。条件变量一般和互斥量一起使用,因为需要保证多线程互斥地修改条件。条件变量阻塞的是线程,不像互斥锁阻塞的是资源。

读写锁

读写锁的核心思想是:将线程访问共享数据时发出的请求分为两种,分别是:

读请求:只读取共享数据,不做任何修改;

写请求:存在修改共享数据的行为。

当前读写锁的状态发起读请求发起写请求
无锁允许允许
读锁允许阻塞等待
写锁阻塞等待阻塞等待

其实核心的要点就是,多个读的进程由于并不改变原内容,所以可以多个并行,但一旦设计到写,那就需要等待一下,让写完了,数据内容不变了再进行操作。


如何避免死锁 ?

产生死锁的原因:当进程空间中的某公共资源不允许多个线程同时访问时,某线程访问公共资源后不及时释放资源,就很可能产生线程死锁。

解决办法

使用互斥锁的时候,用完后及时地解锁;使用信号量时,用完后及时 sem_post();读写锁也是一样,及时解锁。

另外,多个线程请求资源的顺序最好保持一致。线程 t1 需要先请求 mutex 锁然后再请求 mutex2 锁,而 t2 需要先请求 mutex2 锁然后再请求 mutex 锁,这是一种典型的因“请求资源顺序不一致”,两个线程之间会相互等待对方解锁而发生死锁。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程同步的实现 的相关文章

  • kali爆破WiFi

    kali暴力破解WiFi密码实验步骤 建议在kali的root模式下进行 实验条件 xff1a window7下的VMware装的kali 2020 某宝买的无线 网卡 xff0c 注意 xff0c 有些厂家的网卡不支持Linux系统 xf
  • Nacos全面解析

    目录 一 Nacos概要 1 Nacos简介 2 Nacos安装 3 Nacos配置外部数据源 二 Nacos应用 1 注册发现 2 负载均衡 3 配置中心 4 多环境切换 5 配置刷新 6 共享 扩展配置 7 灰度发布 一 Nacos概要
  • Mysql性能调优

    目录 一 Mysql架构设计 1 Mysql架构图 二 Mysql常见的存储引擎 1 InnoDB引擎 2 MyISAM存储引擎 3 MEMORY存储引擎 三 Mysql索引 1 索引的分类 2 mysql中空值和null的区别 3 mys
  • Elasticsearch模糊查询精讲

    目录 一 3种模糊查询的区别 以及 常用的查询手段 1 wildcard 2 prefix 3 fuzzy 4 exists query 二 terms set 主要用于数组检索 1 数据准备 2 minimum should match
  • JRebel激活教程

    1 在idea的plugins中搜索jrebel进行安装插件 2 重启idea 3 激活jrebel 下载代理地址 Release v1 4 ilanyu ReverseProxy GitHub 双击下载的exe文件 在idea中找到激活界
  • Invalid bound statement (not found)异常解决

    问题描述以及解决思路 xff1a 1 我这里用的若依微服务框架 2 新建一个user模块ruoyi user 3 启动后报错mapper映射有问题Invalid bound statement not found 截图如下 4 报错后就开始
  • Linux防火墙添加白名单

    查看防火墙状态 systemctl status firewalld 开启防火墙 systemctl start firewalld service 开机启动防火墙 systemctl enable firewalld service 关闭
  • stream流对集合进行排序,取集合的topN

    package com tangbb test1 import java util ArrayList import java util Comparator import java util List import java util s
  • 1487 北极通讯网络

    1487 北极通讯网络 这个题就是用来维护一个最短路径吧 也不难 xff0c 就是用来求最小生成树的第k条边 并且 xff0c 这貌似和并查集还有一定的关系 我太傻了 xff0c 这里生成树的方法就是krusal xff0c 我居然想 xf
  • mybatis实现批量插入

    1 首先 创建一个简单的insert语句 lt insert id 61 insertname gt insert into names name values value lt insert gt 2 然后在java代码中像下面这样执行批
  • java实现简单的用户登录包含token

    要实现简单的用户登录包含token xff0c 可以按照以下步骤进行 xff1a 创建一个用户表 xff0c 在表中存储用户的用户名和密码等信息 当用户登录时 xff0c 首先检查用户提交的用户名和密码是否正确 xff0c 如果正确则生成一
  • tar.gz压缩,查看,解压

    压缩 span class token function tar span czf jpg tar gz jpg 将目录里所有jpg文件打包成jpg tar后 xff0c 并且将其用gzip压缩 xff0c 生成一个gzip压缩过的包 xf
  • C++调用shell脚本并传递参数

    C 43 43 调用脚本的两种方式 1 system 执行shell命令也就是向dos发送一条指令 xff0c system 会调用fork 产生子进程 xff0c 由子进程来调用 bin sh c string来执行参数string字符串
  • 学生成绩信息管理系统 C++(含源码)

    懂的都懂 经典大一大作业 我以前也是面向CSDN和GitHub搞定了 今天也来造福新的小朋友了 一 系统需求分析 学生成绩信息记录了学生的基本信息和成绩情况 包括 姓名 学号 整数 中间无分隔符 性别 院系 班级 各个学科及其成绩 总成绩
  • Vue项目导入ElementUI后,网页无显示,打不开Vue的Main页面

    错误如下 xff0c 这是因为我们的vue版本和node js版本太高了 xff0c 我们需要使用Element plus 执行 cnpm install save element plus 然后字main js中修改 import Ele
  • 递归求n的阶乘完整版

    递归函数 求n的阶乘 span class token keyword def span span class token function fun span span class token punctuation span Num sp
  • Ubuntu基础使用指南

    一 基础操作 1 终端重要热键 Tab xff1a 补全功能 Ctrl 43 c xff1a 中断目前程序 Ctrl 43 d xff1a 键盘输入结束 Ctrl 43 Shift 43 c xff1a 复制 Ctrl 43 Shift 4
  • Fixed Frame [base_link] does not exist

    出现这个问题请先检查有没有启动gazebo xff0c gazebo是否能正常加载模型 如果不能 xff0c 那么rviz很可能也加载不了相应的节点 启动了gazebo xff0c 在Fixed frame后可以选择节点 xff0c 没有启
  • 舵机控制(0°与90°之间反复)

  • 1488 新的开始

    1488 新的开始 新的开始 保证最少的花费 xff0c 那么很明显这就是一个最小生成树了 做题之前盲猜一下 xff0c 这个题应该要跑两遍最小生成树 xff0c 因为他说了使用两种情况 xff0c p和v 在井上修建一个和在各个井中间修建

随机推荐

  • Rancher2.7 + Jenkins CI/CD全流程保姆级最佳实践

    Rancher 43 Jenkins k8s集群 CI CD全流程最佳实践 CI方面 xff0c 官方推荐的视频教程等多是使用极狐Gitlab CI xff0c 但社区版极狐每月仅400分钟构造时间 xff0c 额外购买价格为1000分钟
  • linux驱动----内核模块

    目录 一 引言 二 模块的特点 三 模块程序的构成 必要内容 1 模块入口函数 2 模块出口函数 3 模块许可证声明 非必要内容 1 模块参数 2 模块导出符号 3 模块相关信息 四 模块操作命令 一 引言 我们往往需要对内核的大小进行控制
  • linux驱动----模块符号导出使用

    本文介绍一种 xff0c 一个模块调动另一个模块中定义的函数的方法 在内核中 xff0c 函数又叫做符号 xff0c 为了便于理解 xff0c 此处说成我们在C语言中熟悉的函数调用 xff0c 回想一下C语言中的函数调用 xff0c 如果我
  • gcc的编译流程

    xff08 1 xff09 预处理阶段 xff08 c gt i xff09 预处理器会处理所有的以 开头的命令 xff0c 这些都属于预处理命令 xff0c 也就是会将所有的头文件包含的东西插入到源文件中 xff0c 将所有定义的宏都在程
  • Linux驱动----mmap系统调用

    在介绍mmap之前 xff0c 我们首先要了解 xff0c 如果没有mmap xff0c 在我们对一个设备进行操作时 xff0c 内核中其实有映射一份该设备的物理内存地址 xff0c 这是一份虚拟地址 xff0c 但是这是属于内核的 xff
  • STM32的启动流程

    本文主要介绍 xff0c STM32从 CPU 上电复位执行第 1 条指令开始 xff08 汇编文件 xff09 到进入 C 程序 main 函数入口之间的那个部分 基本流程如下 1 确定启动方式 每个STM32的芯片上都有两个管脚BOOT
  • 推挽输出&&开漏输出

    在学习STM32的时候 xff0c 我发现了一个很值得研究学习的问题 xff0c 下面 xff0c 用我的理解来阐述一遍 xff0c 这其中的原理 首先请看电路图 在给GPIO配置输出的时候 xff0c 其有两种工作模式可选 xff0c 分
  • const

    const是一个限定符 xff0c 被const限定的变量其值不会被改变 目录 1 修饰变量 2 修饰指针 3 修饰引用 4 修饰成员函数 5 宏定义 define 和 const 常量 1 修饰变量 指向的变量的值不可改变 xff0c 次
  • 用联合体(union)判断大小端

    1 联合体的概念 联合 xff08 union xff09 是一种节省空间的特殊的类 xff0c 一个 union 可以有多个数据成员 xff0c 但是在任意时刻只有一个数据成员可以有值 当某个成员被赋值后其他成员变为未定义状态 这是因为和
  • 字节对齐(结构体)

    直接从问题入手 xff0c 下面这个结构体 xff0c 占多大内存 xff0c 也就是求sizeof xff08 A xff09 是多少 struct A int a char b double c 这就涉及到一个字节对齐的问题 xff0c
  • mysql 8.0.12安装注意事项

    安装 使用msi方式安装 xff0c win10系统 问题 安装之后想使用命令行的方式 xff0c 不是mysql自带的mysql shell所产生的问题 xff1a mysql 不是内部或外部命令 xff0c 也不是可运行的程序或批处理文
  • 树莓派创建WiFi热点

    0x00 将代码clone到本地 git span class hljs keyword clone span https span class hljs comment github com oblique create ap span
  • 虚函数表和虚函数指针

    虚函数表 vtable 虚函数指针 vptr 虚函数表会出现在一个带有虚函数的类中 xff0c 是属于类的 虚函数表相当于一个数组 xff0c 其中存放的只有虚函数 xff0c 可以是继承而来的 xff0c 也可以是自己本身的 虚函数指针是
  • 在构造函数中使用虚函数可以实现多态吗?

    在构造函数和析构函数中使用虚函数可以实现多态吗 xff1f 不能 因为不管是构造函数还是析构函数 xff0c 其父类和子类的执行都是有一定顺序的 xff0c 拿构造函数来说 xff0c 父类的构造函数会先生成 xff08 一个很简单的道理
  • C++类型转换操作符

    在C语言中其实已有类型转换 xff0c 比如强制类型转换 xff0c 形式上是 type expression 对标C中的强制类型转换 xff0c C 43 43 中按照类型转换的意图对他们进行了分类 xff0c 形式上是 static c
  • 直接插入法排序

    本文试例在VS编译器下可正常使用 算法思想 xff1a 每次从待排序的原始序列中取出一个元素 xff0c 将其排入一个新的有序数列中 xff0c 这实际上是一个有序子序列不断增长的过程 xff0c 当有序子序列与原序列的长度一致时 xff0
  • 编译器的差别gcc和VS

    问题的由来是我写了一个排序算法程序 xff0c 在gcc编译器下运行 xff0c 发现结果有问题 xff0c 然后开展的寻找错误解决问题 这是我写的一个简单的插入排序算法 include lt stdio h gt 直接插入法排序函数主体
  • 计算机程序内存分布

    计算机存储器 计算机中有两种存储器 xff0c RAM和ROM RAM xff1a 随机存取存储器 xff08 random access memory xff09 xff0c 也叫主存 内存 它可以随时读写 xff0c 而且速度很快 xf
  • linux下的线程thread

    何为线程 xff1f 线程是一种轻量级的进程 xff0c 一个进程至少包含 1 个线程 xff0c 也可以包含多个线程 xff0c 所有线程共享进程的资源 xff0c 各个线程也可以拥有属于自己的私有资源 其实 xff0c 进程仅负责为各个
  • 线程同步的实现

    互斥锁 互斥锁的使用就是当有线程访问进程空间中的公共资源时 xff0c 线程执行 加锁 操作 将资源锁起来 xff0c 阻止其它线程的访问 访问完成后 xff0c 该线程 xff08 谁锁上的必须由谁来解锁 xff09 负责完成 解锁 操作