多线程(3): 线程同步

news2024/11/17 0:32:11

1. 互斥锁的加锁和解锁

1.1 加锁解锁说明

在处理线程同步时,第一种方式就是使用互斥锁互斥锁只能同时被一个线程使用,锁的所有权只能被一个线程拥有。互斥锁是线程同步最常用的一种方式,通过互斥锁可以锁定一个代码块 ,被锁定的这个代码块,所有的线程只能顺序执行 (不能并行处理),这样多线程访问共享资源数据混乱的问题就可以被解决了,需要付出的代价就是执行效率的降低,因为默认临界区多个线程是可以并行处理的,现在只能串行处理。

在 Linux 中互斥锁的类型为 pthread_mutex_t,创建一个这种类型的变量就得到了一把互斥锁:

pthread_mutex_t  mutex;

在创建的锁对象中保存了当前这把锁的状态信息:锁定还是打开,如果是锁定状态还记录了给这把锁加锁的线程信息(线程 ID)。一个互斥锁变量只能被一个线程锁定,被锁定之后其他线程再对互斥锁变量加锁就会被阻塞,直到这把互斥锁被解锁,被阻塞的线程才能被解除阻塞。一般情况下,每一个共享资源对应一个把互斥锁,锁的个数和线程的个数无关。

Linux 提供的互斥锁操作函数如下,如果函数调用成功会返回 0,调用失败会返回相应的错误号:

// 初始化互斥锁
// restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           const pthread_mutexattr_t *restrict attr);
// 释放互斥锁资源            
int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数:

  • mutex: 互斥锁变量的地址
  • attr: 互斥锁的属性,一般使用默认属性即可,这个参数指定为 NULL
  • restrict: 是一个关键字, 用来修饰指针,只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的。比如说将 p=mux,虽然将互斥锁变量赋值给了p,但p不能访问该互斥锁指向的内存地址。
// 修改互斥锁的状态, 将其设定为锁定状态, 这个状态被写入到参数 mutex 中
int pthread_mutex_lock(pthread_mutex_t *mutex);

这个函数被调用,首先会判断参数 mutex 互斥锁中的状态是不是锁定状态:

  • 没有被锁定,是打开的,这个线程可以加锁成功,这个这个锁中会记录是哪个线程加锁成功了
  • 如果被锁定了,其他线程加锁就失败了,这些线程都会阻塞在这把锁上
  • 当这把锁被解开之后,这些阻塞在锁上的线程就解除阻塞了,并且这些线程是通过竞争的方式对这把锁加锁,没抢到锁的线程继续阻塞
// 对互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

不是所有的线程都可以对互斥锁解锁,哪个线程加的锁,哪个线程才能解锁成功。

互斥锁加锁和解锁操作,需要成对出现。从互斥锁加锁到解锁包括的区域称为临界区,如何确定临界区的范围,首先需要找到共享资源(多线程同时读写的变量),然后将共享资源上下文中涉及到处理共享资源的代码,作为临界区。 临界区内的代码是串行执行的,虽然牺牲了执行效率,但可以确保访问资源的安全。

// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
  • pthread_mutex_trylockpthread_mutex_lock,都可以对互斥锁加锁,主要区别是:如果互斥锁没有被锁住的情况下,这两种操作都可以对互斥锁加锁,但是如果这把互斥锁已经锁上了,此时线程调用pthread_mutex_lock就堵塞到这把互斥锁了。而线程调用pthread_mutex_trylock,表示尝试给这把互斥锁加锁,如果加锁失败了的话,还可以继续干一会其他事情,然后再重新尝试给互斥锁加锁。总之:pthread_mutex_lock在互斥锁已经加锁的时候,线程会堵塞在那,然后一直死等,等待重新尝试上锁,而pthread_mutex_trylock则会比较灵活,它发现无法加锁,拿不到cpu时间片,就回去干些其他事情,事情干完之后,再回来尝试对互斥锁进行重新解锁。

这些互斥锁的加锁和解锁操作,都有一个参数pthread_mutex_t *mutex, 这个对象时通过mutex进行pthread_mutex_init初始化得到的,因此在做线程同步的时候,一定要先通过pthead_mutex_t创建 一个互斥锁mutex,然后调用pthread_mutex_init对创建的互斥锁进行初始化,并且在做线程锁同步期间,这个锁的资源是不能被释放的

1.2 互斥锁使用

我们可以将:多线程(2):线程同步中多线程交替数数的例子修改一下,使用互斥锁进行线程同步。两个线程一共操作了同一个全局变量,因此需要添加一互斥锁,来控制这两个线程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>

#define MAX 100
// 全局变量
int number;

// 创建一把互斥锁
// 全局变量, 多个线程共享
pthread_mutex_t mutex;

// 线程处理函数
void* funcA_num(void* arg)
{
    for(int i=0; i<MAX; ++i)
    {
        // 如果线程A加锁成功, 不阻塞
        // 如果B加锁成功, 线程A阻塞
        pthread_mutex_lock(&mutex);
        int cur = number;
        cur++;
        usleep(10);
        number = cur;
        pthread_mutex_unlock(&mutex);
        printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);
    }

    return NULL;
}

void* funcB_num(void* arg)
{
    for(int i=0; i<MAX; ++i)
    {
        // a加锁成功, b线程访问这把锁的时候是锁定的
        // 线程B先阻塞, a线程解锁之后阻塞解除
        // 线程B加锁成功了
        pthread_mutex_lock(&mutex);
        int cur = number;
        cur++;
        number = cur;
        pthread_mutex_unlock(&mutex);
        printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);
        usleep(5);
    }

    return NULL;
}

int main(int argc, const char* argv[])
{
    pthread_t p1, p2;

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 创建两个子线程
    pthread_create(&p1, NULL, funcA_num, NULL);
    pthread_create(&p2, NULL, funcB_num, NULL);

    // 阻塞,资源回收
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    // 销毁互斥锁
    // 线程销毁之后, 再去释放互斥锁
    pthread_mutex_destroy(&mutex);

    return 0;
}
  • 使用互斥锁pthread_mutex_t,需要引入头文件<pthread.h>,为了保证在线程同步期间,互斥锁资源一直存在,因此这里创建了全局变量的互斥锁。

  • 数数的过程中,很明显共享资源/临界资源就是变量number, 在做线程同步的时候,我们需要确定的是我们要找到的临界区范围肯定是越小越好,因为临界区内只允许一个线程同时执行,所以多线程是串行执行的,执行效率比较低,所以我们要缩小代码块的范围。对于funcA_num这个任务函数,可以看到for循环内的代码都很临界资源number有关系,其中printf这句不加也可以。因此在临界区开始位置加锁操作 pthread_mutex_lock(&mutex),在临界区下面加锁解锁操作pthread_mutex_unlock(&mutex)
    在这里插入图片描述

  • 同理对于funcB_num这个任务函数,它的临界区也是操作共享资源number这部分代码块,其中usleep由于不涉及操作共享资源,因此临界区可以不包括usleep:
    在这里插入图片描述

  • 接下来分析下这个多线程的执行过程,funcA_umfuncB_num这两个任务函数分别是由两个线程p1p2来执行的。假设线程p1先抢到cpu的时间片,然后向下执行,然后执行到pthread_mutex_lock加锁成功,继续向下执行到usleep,usleep使得线程强制放弃了cpu时间片。休眠的过程中,线程p2抢到了cpu时间片, 执行任务函数funcB_num,执行到pthread_mutex_lock(&mutex),但是这个互斥锁mutex已经被线程p1给锁定了,线程p2就被阻塞了,只能在那死等,在阻塞期间它就放弃了cpu资源。线程p1睡醒了之后,又抢到了cpu时间片,继续从原来的位置向下执行,执行完成之后,通过pthread_mutex_unlock(&mutex)把互斥锁mutex解锁,解锁后线程p2就被解除堵塞了,解除堵塞后,线程p2马上就抢到了cpu时间片,然后通过pthread_mutex_lock(&mutex)把锁重新锁上。然后执行funcB_num中数数的代码,在数数期间,线程p1可能又抢到时间片,但它不能执行funcA_um中数数的代码,因为锁已经让线程p2锁上了,线程p1只能堵塞在这把互斥锁上。

  • 从前面的分析可以知道,线程p1和线程p2在访问共享资源number时是线性执行(顺序或串行),而不是并行执行。既然是顺序执行,就不存在同时访问共享资源的情况,那么数据就不会发生错落了。

另外互斥锁mutex变量需要在线程调用前进行初始化,不然的话会导致线程在使用互斥锁出现问题。

// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
  • 当线程做完同步,并且线程资源已经被回收了,此时互斥锁就没有存在的意义了,就需要将互斥锁的资源释放掉。
   // 销毁互斥锁
    // 线程销毁之后, 再去释放互斥锁
    pthread_mutex_destroy(&mutex);

  • 最后执行编译,两个线程同时数数,每个线程各数50次,可以正常数到100,从打印的结果可以看出,两个线程并不是交替执行,同一个线程可能连续数3次,可能间隔一段时间才数数,这是因为线程抢到cpu资源是随机的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/737753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

23款奔驰S450 4MATIC更换原厂旋转高音,提升车内氛围感

奔驰加装3D旋转高音&#xff0c;让高音“有型有色”,高端3D环绕立体声音响系统的视觉效果同样令人印象深刻&#xff1a;系统启动时&#xff0c;安装在前车门后视镜三角板中的两个高音头会与同色车内氛围灯一块亮起&#xff0c;同时向外旋出10mm至最佳效果位置&#xff0c;以提高…

docker安装单机版nacos,并把数据保存到MySQL

1.下载镜像(请根据cloud版本选择) docker pull nacos/nacos-server:1.4.12.启动临时镜像并拷贝文件 docker run -p 8848:8848 -p 9848:9848 -p 9849:9849 --name nacos-temp \ -d nacos/nacos-server:1.4.1后面是需要修改的本机路径 docker cp nacos-temp:/home/nacos/logs/…

记录一个heatmap.js在strict模式下的bug

ImageData的data属性只读&#xff0c;无法修改 出问题的在原始代码的490行~528行 var img this.shadowCtx.getImageData(x, y, width, height);var imgData img.data;var len imgData.length;var palette this._palette;for (var i 3; i < len; i 4) {var alpha imgD…

拥有铁粉,怀抱CSDN大家庭

&#x1f451; 个人主页 &#x1f451; &#xff1a;&#x1f61c;&#x1f61c;&#x1f61c;Fish_Vast&#x1f61c;&#x1f61c;&#x1f61c; &#x1f41d; 个人格言 &#x1f41d; &#xff1a;&#x1f9d0;&#x1f9d0;&#x1f9d0;说到做到&#xff0c;言出必行&am…

收藏备用 | 提高效率的建筑工地技巧

随着城市化进程的加速和建筑业的蓬勃发展&#xff0c;建筑工地扬尘和噪声污染成为了日益突出的问题。这些问题不仅对周边环境造成了不良影响&#xff0c;还对居民的生活质量和健康造成了潜在风险。 为了有效管理和监控建筑工地的扬尘和噪声水平&#xff0c;保障周边居民的权益和…

ROS:rqt工具箱

目录 一、概念二、作用三、rqt安装启动3.1安装3.2启动 一、概念 ROS基于 QT 框架&#xff0c;针对机器人开发提供了一系列可视化的工具&#xff0c;这些工具的集合就是rqt 二、作用 方便的实现 ROS 可视化调试&#xff0c;并且在同一窗口中打开多个部件&#xff0c;提高开发…

nginx简单项目部署

后台可以连接idea服务也可以部署到java上这里不展开 nginx官网下载安装 启动命令&#xff1a; start nginx 或者双击nginx.exe 重启命令 &#xff1a;nginx -s reload 修改配置文件 E:\dev\nginx\nginx-1.22.1\conf\nginx #user nobody; worker_processes 1;#error_log lo…

[Unity实战]EnhancedScroller v2.21.4简单使用[开箱可用]

[Unity实战]EnhancedScroller v2.21.4简单使用[开箱可用] EnhancedScroller v2.21.4 简单使用EnhancedScroller是什么?1.处理UI1.1 加入Canvas1.2 Canvas/Scroller --> 空对象1.3 Scroller加入组件 EnhancedScroller1.4 拖拽Scroller1.5 Scroller/GameObject --> 拖拽到…

GPIO8种工作模式

前言&#xff1a; GPIO是单片机通用的输入输出引脚&#xff0c;基本用途可作为开关&#xff0c;常用于控制LED亮灭、蜂鸣器的鸣响、电机的转停&#xff0c;但由于驱动能力不够&#xff0c;常常要与三极管一起使用。其它的高级用途如I/O作为输入引脚&#xff0c;可检测外部的中…

「解决」pip install xxx 解释器错误: 没有那个文件或目录

bash: /home/raywit/anaconda3/envs/xxx/bin/pip: /home/another/anaconda3/envs/xxx/bin/python: 解释器错误: 没有那个文件或目录 上图是当我拷贝别人环境时发现少包&#xff0c;然后继续pip时出现这样的错&#xff0c;根本原因则是使用pip时路径没修改成自己的。&#xff08…

金九银十互联网大厂Java面试1000问,覆盖一线大厂各种面试痛点

不知不觉马上要到金九银十的跳槽黄金月&#xff0c;跳槽结果有人欢喜有人愁&#xff0c;找到好的下家固然可喜&#xff0c;跳槽结果不理想的朋友也不必丧气&#xff0c;只要扎实提升自己的技术&#xff0c;弄明白大厂面试官的出题逻辑&#xff0c;进大厂必是水到渠成。 之前有…

任天堂 Switch 六月销量破纪录,极有可能成为日本市场销量冠军

任天堂于2017年发布的游戏机Switch已进入第六个年头。虽然该游戏机在硬件性能和品控方面受到一些评价不佳&#xff0c;但销售数据表明绝大多数玩家仍然乐意购买Switch游戏机。 根据日本经济新闻报道&#xff0c;今年6月Switch在日本销售了380,000台机器&#xff0c;同比增长68%…

单片机第一季:零基础3

目录 1&#xff0c;第五章 2&#xff0c;第六章 1&#xff0c;第五章 IDE概念&#xff1a; IDE就是集成开发环境&#xff0c;就是一套用来开发的完整的软件系统。 Keil和MDK&#xff1a; (1)本来只能用来开发51单片机&#xff0c;叫Keil&#xff1b; (2)后来ARM公司收购了Ke…

Python学习笔记(二十)————面向对象

&#xff08;1&#xff09;面向对象的好处 在日常中&#xff0c;记录数据时往往使用统一的表格&#xff0c;这样就不会使得数据信息格式混乱&#xff0c;同样在程序中的数据组织中&#xff0c;仅仅通过变量来记录会显得混乱不统一。 在程序中是可以做到和生活中那样&#xff…

探究Vue源码:mustache模板引擎(5) 对比rollup与webpack,在本地搭建webpack环境

好 从本文开始 我们就来手写一下mustache这个库 他是模板引擎的一个祖先 将模板字符串编译成一个dom字符串 就是它的思想&#xff0c;这也是一个具有跨时代意义的思想 这里的话 我们还是搭一个 webpack 的项目环境 这里值得一提的是 mustache 他官方是通过rollup来进行打包的 …

7-3 种钻石

7-3 种钻石 分数 5 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 2019年10月29日&#xff0c;中央电视台专题报道&#xff0c;中国科学院在培育钻石领域&#xff0c;取得科技突破。科学家们用金刚石的籽晶片作为种子&#xff0c;利用甲烷气体在能量作用下形成碳的等离子体…

抖音seo源码/源代码搭建/源代码部署打包-支持二开

1. 抖音seo源码/源代码搭建/源代码部署打包-支持二开 抖音SEO是指通过提高在抖音平台的关键词排名&#xff0c;来获取流量、获取客户的目的。抖音的流量主要分为付费流量、推荐流量和搜索流量&#xff0c;其中搜索流量因为付费太贵、上热门太累而成为另一种进入方式。与传统搜…

一个UE频繁掉网的问题

这个UE频繁掉网的问题&#xff0c;其实蛮low的&#xff0c;熟悉的人&#xff0c;看一个参数值就搞定这个问题了&#xff0c;但是还是做个记录。问题背景是运营商指定UE锁在某个NR小区&#xff0c;在一个区域的弱信号点(RSRP -110dbm左右)进行TPUT测试&#xff0c;但是最后发现U…

4.日志分布式-ELK

文章目录 日志分布式-ELK概念可以添加的其它组件filebeat 结合 logstash 带来好处为什么要使用 ELK缓存和Fluentd完整日志系统基本特征ELK 的工作原理 部署Elasticsearchjdk环境和防火墙配置安装Elasticsearch修改配置文件优化内存参数启动程序并测试效果安装 Elasticsearch-he…

DTZY-3579-z型三相四线费控智能电能表

智能电能表是现代智能电网中的重要组成部分&#xff0c;它集电能计量、数据采集、远程控制、安全保护等多种功能于一体&#xff0c;为电力系统的高效运行和能源管理提供了有力支持。其中&#xff0c;DTZY-3579-z 型三相四线费控智能电能表是一款性能稳定、功能齐全、安全可靠的…