c语言多线程间共享哪些数据

news2024/11/27 22:43:32

问题描述

一个进程中的多个线程调用了共享库中的函数,共享库中定义了静态变量,该静态变量共享吗?

首先我们需要明确三个问题:

  • 一个进程加载了共享库后,共享库的代码放在哪里?
  • 一个进程的多线程之间会共享哪些数据?
  • 静态局部变量是放在哪个区域?

首先我们来看下进程地址空间中的数据排放顺序:

动态库代码放在哪里? 

动态库在被系统加载后,代码段只有一份,所有使用者(即所有的应用进程)共享。对于数据段要分两种情况:

  • 如果是多进程的话,数据段在每个进程中拥有一个独立的副本,所以数据是安全的;
  • 如果是多线程的话,数据段在同一个进程中是同一个地址空间,所以数据是不安全的;

线程间共享哪些数据?

线程私有资源

线程运行的本质其实就是函数的执行,函数的执行总会有一个源头,这个源头就是所谓的入口函数,CPU从入口函数开始执行从而形成一个执行流,只不过我们人为的给执行流起一个名字,这个名字就叫线程。

既然线程运行的本质就是函数的执行,那么函数执行都有哪些信息呢?在《[函数运行时在内存中是什么样子]》这篇文章中应该提过,函数运行时的信息保存在栈帧中,栈帧中保存了函数的返回值、调用其它函数的参数、该函数使用的局部变量以及该函数使用的寄存器信。

如图所示,假设函数A调用函数B:

CPU执行指令的信息保存在一个叫做程序计数器的寄存器中,通过这个寄存器我们就知道接下来要执行哪一条指令。由于操作系统随时可以暂停线程的运行,因此我们保存以及恢复程序计数器中的值就能知道线程是从哪里暂停的以及该从哪里继续运行了。由于线程运行的本质就是函数运行,函数运行时信息是保存在栈帧中的,因此每个线程都有自己独立的、私有的栈区。

同时函数运行时需要额外的寄存器来保存一些信息,像部分局部变量之类,这些寄存器也是线程私有的,一个线程不可能访问到另一个线程的这类寄存器信息

从上面的讨论中我们知道,到目前为止,所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器是线程私有的。

以上这些信息有一个统一的名字,就是线程上下文,thread context。我们也说过操作系统调度线程需要随时中断线程的运行并且需要线程被暂停后可以继续运行,操作系统之所以能实现这一点,依靠的就是线程上下文信息。

代码区

进程地址空间中的代码区,这里保存的是什么呢?从名字中有的同学可能已经猜到了,没错,这里保存的就是我们写的代码,更准确的是编译后的可执行机器指令

那么这些机器指令又是从哪里来的呢?答案是从可执行文件中加载到内存的,可执行程序中的代码区就是用来初始化进程地址空间中的代码区的。

线程之间共享代码区,这就意味着程序中的任何一个函数都可以放到线程中去执行,不存在某个函数只能被特定线程执行的情况

数据区

进程地址空间中的数据区,这里存放的就是所谓的全局变量。什么是全局变量?所谓全局变量就是那些你定义在函数之外的变量,在C语言中就像这样:

其中字符c就是全局变量,存放在进程地址空间中的数据区。

在程序员运行期间,也就是run time,数据区中的全局变量有且仅有一个实例,所有的线程都可以访问到该全局变量。值得注意的是,在C语言中还有一类特殊的“全局变量”,那就是用static关键词修饰过的变量,就像这样:

 

注意到,虽然变量a定义在函数内部,但变量a依然具有全局变量的特性,也就是说变量a放在了进程地址空间的数据区域,即使函数执行完后该变量依然存在,而普通的局部变量随着函数调用结束和函数栈帧一起被回收掉了,但这里的变量a不会被回收,因为其被放到了数据区。
这样的变量对每个线程来说也是可见的,也就是说每个线程都可以访问到该变量。

堆区是程序员比较熟悉的,我们在C/C++中用malloc或者new出来的数据就存放在这个区域,很显然,只要知道变量的地址,也就是指针,任何一个线程都可以访问指针指向的数据,因此堆区也是线程共享的属于进程的资源。(因为线程间共享地址空间,所以无论哪个线程访问这个地址都是指向同一块内存空间) 

 栈区

唉,等等!刚不是说栈区是线程私有资源吗,怎么这会儿又说起栈区了?确实,从线程这个抽象的概念上来说,栈区是线程私有的,然而从实际的实现上看,栈区属于线程私有这一规则并没有严格遵守

这句话是什么意思?

通常来说,注意这里的用词是通常,通常来说栈区是线程私有,既然有通常就有不通常的时候。不通常是因为不像进程地址空间之间的严格隔离,线程的栈区没有严格的隔离机制来保护

因此如果一个线程能拿到来自另一个线程栈帧上的指针,那么该线程就可以改变另一个线程的栈区,也就是说这些线程可以任意修改本属于另一个线程栈区中的变量。

 

这从某种程度上给了程序员极大的便利,但同时,这也会导致极其难以排查到的bug。

试想一下你的程序运行的好好的,结果某个时刻突然出问题,定位到出问题代码行后根本就排查不到原因,你当然是排查不到问题原因的,因为你的程序本来就没有任何问题。

是别人的问题导致你的函数栈帧数据被写坏从而产生bug,这样的问题通常很难排查到原因,需要对整体的项目代码非常熟悉,常用的一些debug工具这时可能已经没有多大作用了。

说了这么多,那么同学可能会问,一个线程是怎样修改本属于其它线程的数据呢?

首先我们在主线程的栈区定义了一个局部变量,也就是 int a= 1这行代码,现在我们已经知道了,局部变量a属于主线程私有数据,但是,接下来我们创建了另外一个线程。

在新创建的这个线程中,我们将变量a的地址以参数的形式传给了新创建的线程,然后我来看一下thread函数。在新创建的线程中,我们获取到了变量a的指针,然后将其修改为了2

也就是这行代码,我们在新创建的线程中修改了本属于主线程的私有数据。

现在你应该看明白了吧,尽管栈区是线程的私有数据,但由于栈区没有添加任何保护机制,一个线程的栈区对其它线程是可以见的,也就是说我们可以修改属于任何一个线程的栈区。

就像我们上文说得到的,这给程序员带来了极大便利的同时也带来了无尽的麻烦,试想上面这段代码,如果确实是项目需要那么这样写代码无可厚非。

但如果上述新创建线程是因bug修改了属于其它线程的私有数据的话,那么产生问题就很难定位了,因为bug可能距离问题暴露的这行代码已经很远了,这样的问题通常难以排查。

 动态链接库

进程地址空间中除了以上讨论的这些实际上还有其它内容,还有什么呢?这就要从可执行程序说起了。

什么是可执行程序呢?

在Windows中就是我们熟悉的exe文件,在Linux世界中就是ELF文件,这些可以被操作系统直接运行的程序就是我们所说的可执行程序。

那么可执行程序是怎么来的呢?有的同学可能会说,废话,不就是编译器生成的吗?实际上这个答案只答对了一半。

假设我们的项目比较简单只有几个源码文件,编译器是怎么把这几个源代码文件转换为最终的一个可执行程序呢?

原来,编译器在将可执行程序翻译成机器指令后,接下来还有一个重要的步骤,这就是链接,链接完成后生成的才是可执行程序。完成链接这一过程的就是链接器。

其中链接器可以有两种链接方式,这就是静态链接动态链接。静态链接的意思是说把所有的机器指令一股脑全部打包到可执行程序中,动态链接的意思是我们不把动态链接的部分打包到可执行程序,而是在可执行程序运行起来后去内存中找动态链接的那部分代码,这就是所谓的静态链接和动态链接。

动态链接一个显而易见的好处就是可执行程序的大小会很小,就像我们在Windows下看一个exe文件可能很小,那么该exe很可能是动态链接的方式生成的

而动态链接的部分生成的库就是我们熟悉的动态链接库,在Windows下是以DLL结尾的文件,在Linux下是以so结尾的文件。说了这么多,这和线程共享资源有什么关系呢?

原来如果一个程序是动态链接生成的,那么其地址空间中有一部分包含的就是动态链接库,否则程序就运行不起来了,这一部分的地址空间也是被所有线程所共享的。

 也就是说进程中的所有线程都可以使用动态链接库中的代码。

文件

如果程序在运行过程中打开了一些文件,那么进程地址空间中还保存有打开的文件信息,进程打开的文件也可以被所有的线程使用,这也属于线程间的共享资源。

多线程的地址空间分布 

每个线程有自己独立的stack,但是heap是共享的。

线程stack上的变量或对象不会被另外一个线程看到,除非主动告诉其这个变量或对象的地址,因为相同进程里的线程栈都是在同一个地址空间上,所以该地址在另外一个线程也是有效的。 

 

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

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

相关文章

Appium + mitmProxy 实现APP接口稳定性测试

随着 App 用户量的不断增长,任何小的问题都可能放大成严重的线上事故,为了避免对App造成损害的任何可能性,我们必须从各个方面去思考 App 的稳定性建设,尽可能减少任何潜在的威胁。 1.背景介绍 为了保障 App 的稳定性&#xff0c…

C#,码海拾贝(29)——求解“大型稀疏方程组”的“全选主元高斯-约去消去法”之C#源代码

大型稀疏矩阵线性化方程组的数值求解问题 广泛存在于工程实践尤其是计算机仿真领域 如水力管网计算,电力系统的大型导纳矩阵计算,高阶偏微分方程的数值求解,以及铸件充型过程与凝固过程的数值模拟等。 经常出现在科学和工程计算中, 因此寻找…

【spring源码系列-03】xml配置文件启动spring时refresh的前置工作

Spring源码系列整体栏目 内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428【三】xml配置文件启动spring时refres…

【科技素养题】少儿编程 蓝桥杯青少组科技素养题真题及解析第19套

少儿编程 蓝桥杯青少组科技素养题真题及解析第19套 1、下列现象中有化学变化发生的是 A、蜡烛融化 B、冰块融化 C、电磁炉烧开水 D、铁生锈 答案:D 考点分析:主要考查小朋友们的物理和化学知识,题目问的是化学变化;区别物理变化和化学变化的唯一标志是有无新物质生成…

公司来了个新的测试员,本以为是个菜鸡,没想到......

最近公司来了个新同事,学历并不高,而且大学也不是计算机专业的,今年刚满30岁。。 本以为也是来干点基础的活混混日子的,结果没想到这个人上来就把现有项目的性能测试了一遍,直接给公司节省了不少成本,这种…

C语言/C++新手入门学习经验资料分享

一 学好C语言的运算符和运算顺序 这是学好《C程序设计》的基础,C语言的运算非常灵活,功能十分丰富,运算种类远多于其它程序设计语言。 在表达式方面较其它程序语言更为简洁,如自加、自减、逗号运算和三目运算使表达式更为简单&a…

硅谷最爱的测试框架:详解PyTest

Python中有许多测试框架,但其中最受欢迎的就是PyTest。PyTest是一个强大而灵活的测试框架,它提供了许多先进的功能,可以让你的测试更加简洁、易读。 一、PyTest 简介 PyTest是一个开源的Python测试框架,用于编写简单而丰富的测试…

FreeRTOS_任务基础知识

目录 1. 什么是多任务系统? 2. FreeRTOS 任务与协程 2.1 任务 (Task) 的特性 2.2 协程(Co - routine)的特性 3. 任务状态 4. 任务优先级 5. 任务实现 6. 任务控制块 7. 任务堆栈 RTOS 系统的核心就是任务管理,FreeRTOS 也…

软件测试想要高薪资,不仅要卷还要学会跳槽

都说00后躺平了,但是有一说一,该卷的还是卷。 这不,前段时间我们公司来了个00后,工作都没两年,跳槽到我们公司起薪20K,都快接近我了。后来才知道人家是个卷王,从早干到晚就差搬张床到工位睡觉了…

C++ new和delete的使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、new和delete介绍二、简单使用1.new和delete2.自定义对象3.new[]和delete[]4.主存耗尽5.try&catch6.nothrow7.看下源代码 前言 new和delete是C里非常重…

【运维】服务器系统安装

目录 一、环境 二、ubuntu 三、启动u盘制作 Stage 1:下载balena,制作U盘启动工具 Stage 2:下载Ubuntu 系统镜像(参考上一节:Ubuntu 22.04.2 LTS ) Stage 3:将镜像写入到U盘 四、设置开启…

6 面阿里、5 面字节、4 面腾讯,可算是入坑了····

8 年前,BAT 冲到了风口浪尖,美国上市的阿里成为中国体量最大的互联网公司,腾讯借助微信成为移动互联网的霸主,外企开始撤离中国,国企的光环也慢慢褪去。 到了近年,应届毕业生心中最炙手可热的公司换成了 T…

共话出海、布局全球,融云WICC2023 · 泛娱乐出海嘉年华广州收官!

(移步公众号点击图片三折购买《社交泛娱乐出海作战地图》) 6 月 2 日,“WICC 泛娱乐出海嘉年华”在广州成功举办,圆满收官。关注【融云全球互联网通信云】了解更多 本届嘉年华由高端峰会、圆桌会议、露营派对三部分组成&#xf…

资深8年测试,全链路压测与性能的优化详解,一文通透...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 什么是全链路压测…

了解CPU瓶颈原因,掌握代码优化、TOP命令及缓存技术,让服务器不再为性能瓶颈所困扰。

目录 前言: 一、CPU瓶颈原因分析 1. CPU使用率过高 2. 进程使用CPU过多 3. 磁盘I/O读写速度过慢 二、CPU瓶颈调优方案 1. 使用top命令查看CPU使用率 2. 优化程序设计 3. 使用缓存技术 总结: 前言: 在服务器运行过程中,…

干货 | 实战演练基于加密接口测试测试用例设计

如果接口测试仅仅只是掌握一些requests或者其他一些功能强大的库的用法,是远远不够的,还需要具有根据公司的业务以及需求去定制化一个接口自动化测试框架能力。所以在这个部分,会主要介绍接口测试用例分析以及通用的流程封装是如何完成的。 首…

GitHub 竟然有这些骚操作,真是涨姿势

GitHub 竟然有这些骚操作,真是涨姿势 GitHub,不用过多介绍。一个面向开源及私有软件项目的托管平台,因为只支持 git 作为唯一的版本库格式进行托管,故名 GitHub。 作为「全球最大的程序员“交友”社区」,程序员的你&am…

入门AI从谷歌这10门独立课程开始

​ 谷歌最近发布了一个名为"Generative AI learning path"的学习路径,该路径专为初学者设计,共包含10门独立课程。通过这个学习路径,初学者可以从基础概念开始学习,并逐步深入到更复杂的主题,帮助他们了解生…

NLP实战:调用Gensim库训练Word2Vec模型

目录 一、准备工作 1. 安装Gensim库 2. 对原始语料分词 二、训练Word2Vec模型 三、模型应用 1.计算词汇相似度 ​编辑 2. 找出不匹配的词汇 3. 计算词汇的词频 四、总结 🍨 本文为[🔗365天深度学习训练营]内部限免文章(版权归 *K同学…

第7章:SpringMVC的HttpMessageConverter

1. HttpMessageConverter简介 ①HttpMessageConverter,报文信息转换器,将请求报文转换为java对象,或将java对象转换为响应报文 ②HttpMessageConverter提供了两个注解和两个类型 RequestBody,ResponseBody,RequestEntity,ResponseEntity …