【Linux多线程编程】3. 多线程共享资源

news2024/11/16 11:47:54

回顾

上篇文章【Linux多线程编程】2.线程创建与回收 简单介绍了如何创建一个线程并且回收它,末尾给出了如下这段代码,本文将从这段代码入手介绍线程资源、线程共享资源、线程独占资源,并在最后引出多线程安全访问资源的方法。

/*
 * test_pthread_worker.c
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>             /* for sigaction */
#include <errno.h>
#include <unistd.h>

int total = 0; // 1

static void *producer_thread(void *UnusedArg) {
  while (1) {
    sleep(1); // 2
    total += 5;
    printf("%s produce 5 tools, now total %d\n", __func__, total);
  }
  return NULL;
}

static void *consumer_thread(void *UnusedArg) {
  while (1) {
    sleep(1); // 3
    total -= 5;
    printf("%s consume 5 tools, now total %d\n", __func__, total);
  }
  return NULL;
}

int main()
{
  int rc;
  pthread_t producer;
  pthread_t consumer;

  rc = pthread_create(&producer, NULL, producer_thread, NULL);
  if (rc != 0) {
    printf("Could not create producer_thread\n");
    return -1;
  }

  rc = pthread_create(&consumer, NULL, consumer_thread, NULL);
  if (rc != 0) {
    printf("Could not create consumer_thread\n");
    return -1;
  }

  pthread_join(producer, NULL);
  pthread_join(consumer, NULL);
  return 0;
}

线程资源

上文我们了解到,线程是运行在进程里的,通常由进程创建并负责回收。一个线程产生后是要去执行一段代码的,执行完毕后线程退出,被进程回收。
线程想要成功运行代码,必须获取一些资源,包括但不限于CPU资源、内存资源、硬件资源等。这些资源由线程所在的进程提供,从而多个线程运行在同一进程中则会争夺这些资源。
下面列出几种常见的线程间共享资源

堆 :也就是代码中通过malloc/zalloc/calloc 分配的资源
全局变量:例如上例中的total
静态变量:用static声明的变量,包括静态全局变量以及静态局部变量,
打开的文件

线程内还有自己独占的资源,以下列出常见的线程独占资源

线程ID:上例中 pthread_t 类型的变量, 每个线程都有自己唯一的ID,用于区分不同的线程。
线程运行栈:可以理解为线程的执行函数
线程执行函数内声明的普通局部变量

所谓线程共享资源,描述为线程抢占资源更为贴切些,由于资源是共享的,所以线程间就会去争夺这些资源。如果不加一些限制,则多线程并发访问共享资源,极容易引发各种数据问题,甚至造成死锁,阻塞整个进程。

例如在上述代码中,我们的本意是——创建一个生产者,创建一个消费者,生产者不断生产货品,每1s产生5个货品(total),然后将货品放到加工台,由另一名工人(消费者)拿到这些货品进行处理,消费者每拿到5个货品处理的时间为1s,处理完后再次去加工台拿取新的一批货品。
在这里插入图片描述
设想下,如果上图中棕白的小兔子压面饼的速度过快或者过慢会产生什么情况?
很明显,如果速度过快,白色扯面团的小兔子还没有产出新的面团,棕白小兔子就压面饼,从而产生“空的压面饼动作”,换到程序里就是 total 变为了负值;
如果速度过慢,棕色拿剪刀的小兔子就会剪出多余的馅料,但没有新的压好的面饼产生,造成馅料的浪费,想想生产线上都是一坨坨馅料,这不是生产事故了吗?
当然,兔兔并不是机器,在没有面饼的时候可以停下来等待,但程序是机器,设定好既定的操作后就会一直运行,有没有办法让程序拥有兔兔的脑袋等一等呢?这就是下一篇文章我们将介绍的线程同步机制——锁。
但本文中,我们仍然就多线程资源访问不合理造成的问题展开讨论,加深对多线程资源的理解,也就是本文最开始展示的那部分有问题的生产者消费者代码。

代码解释

上例代码中三个比较重要的地方我用注释标注了出来

int total = 0; // 1
static void *producer_thread(void *UnusedArg) {
  while (1) {
    sleep(1); // 2
  }
  //...
}
static void *consumer_thread(void *UnusedArg) {
  while (1) {
    sleep(1); // 3
  }
  //...
}

main函数中我们依次创建了producerconsumer两个线程,然后各自执行producer_thread/consumer_thread两个函数,函数体基本都是一个死循环,然后每隔1s对全局变量total的值+5/-5
这里total就是一个线程共享资源,producerconsumer会去竞争这个资源,谁拿到谁操作,而对total的加/减操作从操作系统底层看可能是如下的伪代码(涉及到寄存器相关知识,这里不做展开,读者只需要知道对变量的加减操作在内核层面并不是“原子的”,而是多个步骤组成,如果在中间某个步骤,其他的执行者切入修改了数据,最终会导致得到的结果不符合预期。所谓原子,指一系列操作要么全部成功,要么全部失败。)

// total +
tmp = total; // 先存储 total 的值
total = tmp + 5; // 再增加 total 的值
// total -
tmp = total;
total = tmp - 5;

现在代入两个线程,再来看上述步骤是否安全。
设某时刻 total = 15
时刻1:producer执行 total += 5,在内核层面,实际上理解为tmp = total; total = total + 5两个步骤。
时刻2:producer刚执行完tmp=total;consumer开始执行total -= 5,并且将 total -= 5执行完毕
时刻3:此时 total = 10producer继续执行剩下的total = tmp + 5tmp依旧是producer保存的15,执行完毕
时刻4:此时producerconsumer的加减操作都执行完毕,total = 20
上述过程中,total初始值为15,producer和consumer各运转一次,也就是预期最终值total=15;
但是确由于非原子性的操作,造成数据混乱,最终total=20。

上述只是介绍了线程对于共享资源处理的其中一种可能导致的结果,更多的读者可以自行发散。
现在回到我们的代码中,运行后会发现出现了 total 值为负数的情况,根据上述的理论也解释的通了。
有没有办法能解决多线程并发访问共享资源带来的上述问题吗?
有!
这就是我们下节要介绍的线程锁。具体内容在下节展开。

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

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

相关文章

新华三(H3C)的沉浮往事

根据2023年1月3日紫光股份发布的最新公告&#xff0c;Hewlett Packard Enterprise Company全资子公司H3C Holdings Limited&#xff08;“HPE 开曼”&#xff09;和Izar Holding Co&#xff0c;将向紫光股份全资子公司紫光国际信息技术有限公司出售其持有的新华三集团有限公司合…

【Linux】伪目标 PHONY | 探讨项目构建问题 | Makefile | 依赖关系与依赖方法

&#x1f923; 爆笑教程 &#x1f449; 《看表情包学Linux》&#x1f448; 猛戳订阅 &#x1f525; &#x1f4ad; 写在前面&#xff1a;本章我们要学习的是 makefile。会不会写 makefile&#xff0c;从一个侧面说明一个人是否具备完成大型工程的能力。一个工程中的源文件不计…

Vector - VT System - 板卡_VT1004

今天我们来聊一下导入和测量模块VT1004版本&#xff0c;我们从它的技术参数、通道介绍、功能介绍几个方面来全面的介绍这块板卡&#xff0c;废话不多说&#xff0c;我们直接来看这2块板卡吧。 测量模块 - VT1004 通道功能介绍&#xff1a; >通过继电器切换到原始负载和母线…

Qt扫盲-QSet理论总结

QSet理论总结一、概述二、使用1. 声明2. 插入元素3. 遍历元素4. 删除元素5. 集合的运算6. 其他一、概述 QSet是Qt的通用容器类之一。俗称一个集合。QSet会按未指定的顺序存储值&#xff0c;也就是随机存值的方式&#xff0c;并提供非常快速的值查找。在内部&#xff0c;QSet实…

python学习|第二天

文章目录1.函数函数调用函数返回值函数参数2.bug常见类型粗心类型知识点不扎实思路不清被动掉坑常见异常类型3.文件的读写打开模式文件对象常用方法with方法4.os模块操作目录相关函数5.打包成可执行文件1.函数 函数调用 p89&#xff0c;笔记待补 函数返回值 1&#xff09;如…

微信小程序开发过程整理

目录1微信开发相关介绍1.1微信公众平台1.2微信开放平台1.3注意事项2微信小程序开发整体介绍2.1微信小程序简介2.2小程序接入流程3框架简介3.1uni-app简介3.2学习使用uni-app3.3学习微信小程序开发4开发规范5开发示例5.1开发工具5.2开发调试5.2.1导入代码5.2.2项目运行5.2.3在微…

java常见题3

11.二分查找的次数 奇数取 中间那一个作为中值 偶数个取 中间靠左 然后不断模拟这个算法 查找的最多次数&#xff1a;n个元素里最多查找log二N 个元素Log2 128 7 12.equals和hashCode java.lang.Object类中有两个非常重要的方法&#xff1a; public boolean equals(Obje…

YOLOV5模型训练

之前在博文中讲到了YOLOV5的运行,以及转tensorrt. 但是, 一个模型通常需要结合数据训练,才能得到更好的结果. 因此,我们有必要熟悉yolov5的训练过程. 执行训练的过程 Yolov5的github提供了官方的训练脚本. 第一次运行,会自动下载数据集,然后会检测到你的gpu配置,如果不对,…

【数据结构】树

树(Tree) 知识框架 树的定义 树和图一样都是非线性结构&#xff0c;树是n个结点的有限集合&#xff0c;当n0时&#xff0c;称这棵树为空树。 非空树有以下特征&#xff1a; 有且仅有一个称为根的结点。如果n>1, 除根结点以外其它结点可以分为m(m>0)个不相交的集合T1,T…

E4445A频谱分析仪

18320918653 E4445A 名称&#xff1a;E4445A 频谱分析仪&#xff0c; 3 Hz - 13.2 GHz 详细&#xff1a;主要技术指标 性能 /-0.24 dB幅度精度 -155 dBm/Hz显示的平均噪声电平&#xff08;DNAL&#xff09; 10 kHz偏置时的相噪&#xff1a;-118 dBc/Hz 81 dB W-CDMA AC…

春节倒计时,让我来秀一手:用Python制作一个对联生成器

前言 跨年跨完了&#xff0c;马上就要迎来春节了&#xff0c;这不得秀一手&#xff1f; 那就直接开始春节的表演呗 勉勉强强来用python制作对联生成器吧 效果展示 这里的话&#xff0c;你自己想要啥春联主题是可以搜索滴&#xff0c;有些地方也是可以看着改的&#xff0c;…

FPGA知识汇集-FPGA的低功耗设计方法总结

精确的热分析在很多电子产品设计中都有着举足轻重的作用&#xff0c;在高端的PCB设计中尤为突出。热分析的结果常常会影响PCB的机械层设计和产品的外壳设计:是否需要安装散热片、散热风扇等。如果安装散热风扇&#xff0c;往往需要降低其噪音&#xff0c;这将使得机械层设计变得…

【OpenAI】What Is ChatGPT

文章目录介绍注册介绍 OpenAI发布了一个全新的聊天机器人模型—— ChatGPT&#xff0c;同时这也是继GPT-3.5 系列的主力模型之一 ChatGPT 测试地址&#xff1a; https://chat.openai.com/auth/login https://gpt.chatapi.art/ ChatGPT官方说明&#xff1a; Optimizing Langua…

【自学Java】Java语言数组遍历

Java语言数组遍历 Java语言数组遍历教程 Java 语言 中如果我们定义好了数组&#xff0c;并且给数组设置了值&#xff0c;那么怎么样访问数组呢&#xff1f;怎么样获取数组里面的数据值呢&#xff1f;我们可以使用 for 来遍历数组&#xff0c;获取每个位置上的值。 Java语言数…

谷粒学院——第十四章、微信扫码登录

准备工作 注册开发者资质 官网&#xff1a;https://open.weixin.qq.com/ 尚硅谷分享 wx:open:# 微信开放平台 appidappid: wxed9954c01bb89b47# 微信开放平台 appsecretappsecret: a7482517235173ddb4083788de60b90e# 微信开放平台 重定向url&#xff08;guli.shop需要在微…

腾讯云存储

文章目录一、开通腾讯云存储1.注册腾讯云账号&#xff0c;开通对象服务2. 创建存储桶3.获取SecretId 和 SecretKey提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、开通腾讯云存储 在项目钟的图片以及文件需要归档存储。如果归档文件只保存到服务器…

WebRTC学习总结

WebRTC (Web Real-Time Communications) 是一项实时通讯技术&#xff0c;它允许网络应用或者站点&#xff0c;在不借助中间媒介的情况下&#xff0c;建立浏览器之间点对点&#xff08;Peer-to-Peer&#xff09;的连接&#xff0c;实现视频流和&#xff08;或&#xff09;音频流…

二十五、Docker (1)

&#x1f33b;&#x1f33b; 目录一、Docker的概述1.1 为什么要去学习Docker1.2 Docker 概述1.3 Docker的历史1.4 Docker 官网1.5 Docker能做什么1.6 DevOps(开发、运维)二、Docker安装启动&#xff08;官网&#xff09;2.1 Docker 架构2.1.1 镜像&#xff08;image)2.1.2 容器…

DICOM 图像传输:使用 LeadTools 实现 C-Store SCP 服务

文章目录开发环境创建 Qt Widgets 程序设计界面配置 LeadTools 路径编写代码使用 LDicomNet 实现 SCP 的步骤日志输出编写 SCP Server 类编写 SCP Client 类启动 LDicomNet 及启动监听编译程序运行程序发布与部署测试程序界面美化参考开发环境 LeadTools 17Qt 5.15.2 MSVC2019…

【Linux】进程间通信(万字详解) —— 上篇

&#x1f387;Linux&#xff1a; 博客主页&#xff1a;一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 看似不起波澜的日复一日&#xff0c;一定会在某一天让你看见坚持…