多线程中的使用共享变量的问题

简介: 一组并发线程运行在一个进程的上下文中,每个线程都有它自己独立的线程上下文,例如:栈、程序计数器、线程ID、条件码等,每个线程和其它的线程一起共享除此之外的进程上下文的剩余部分,包括整个用户的虚拟地址空间,当然也共享同样的打开的文件的集合。
一组并发线程运行在一个进程的上下文中,每个线程都有它自己独立的线程上下文,例如:栈、程序计数器、线程ID、条件码等,每个线程和其它的线程一起共享除此之外的进程上下文的剩余部分,包括整个用户的虚拟地址空间,当然也共享同样的打开的文件的集合。,这里有一点要特别注意,就是寄存器是从不共享的,而虚拟存储器总是共享的。
有了共享就要防止在对共享变量进行操作的过程中得到一个不可知的值,在Linux内核中有个原子类型与原子操作这么个概念,因为用户态下没有这么一个原子操作存在,那么在我们用户态下就需要要对操作这个共享变量的线程进行同步。为什么要进行同步呢?
因为假设我们在一个程序中有一个全局变量cnt,初始值为0,接下去我们创建了两个线程,完成的功能都是在一个循环中对这个变量进行+1操作,想象一下在这两个线程操作完成后会出现什么状况。

点击(此处)折叠或打开

  1. void *thread(void* value)
  2. {
  3.      int max = *((int*)value)
  4.  
  5.      for(int i=0;imax;i++)
  6.      {
  7.            cnt++;
  8.      }
  9.      return NULL;
  10. }
假设我们这里的max为10000,那么我们想要得到的结果的结果当然是20000,可是在执行之后结果并不是我们所期望的20000,而是一个小于20000的值。为什么会出现这个现象呢?
这里就是我们为什么需要对线程进行同步了。
因为在C语言的层面来说,cnt++就是一条语句,实际上我们在心里默认把它当作了一个原子操作,事实上,就这么一条操作语句,在汇编代码中是分三步执行的:
1)、将这个值从内存中取出来,放入一个寄存器中;
2)、将寄存器中的值进行+1操作;
3)、将寄存器中的值放入内存中去。
img_826e0659726192232e7118065b5ca6f3.png
因为对与多线程来说我们不知道何时会执行哪个线程,所以执行的顺序是不可知的。我们所想的是先让一边执行完,然后再开始执行另外一边。
现在我们不妨将这个问题极端化,也就是两线程交叉执行,假设左边的执行线程为A,右边为B,假设A先执行,A从内存中取出cnt的值,那么现在在R1里的值为0,接下去,A线程被B线程打断,A停止执行,B开始执行,B又从内存中取出cnt的值,现在在R2中的值也为0。然后又轮到A执行,进行加1操作,则R1为1,接下去轮到B执行,进行加1操作,则R2为1。然后A将值写回到内存中,B也将值写回到内存中。这次我们知道内存中的值为1而并非我们所期望的2。
那么怎么能让它进行正确的执行顺序呢?同步,可以用加锁来完成同步操作。

点击(此处)折叠或打开

  1. for(int i=0;imax;i++)
  2. {
  3.     P(&mutex);
  4.     cnt++;
  5.     V(&mutex);
  6. }
在对cnt加1的操作时,对这个操作加锁,这就意味着当下只有这一个线程执行这个操作,其它的线程都得等在外面,等这个线程解锁出来,其他的线程才可以有机会进去。
加锁之后我们再来看看上面的那张图的执行过程,也假设是在一个极端的情况:
A先加锁,然后完成那三个步骤(因为此时只有它一个线程有操作的权限),解锁;现在内存中的值为1,A加锁,然后一样完成三个步骤,解锁;现在内存中的值为2。与所期望的相同。当然了,对于加锁的问题还要防止出现死锁现象,这里就不讨论了。


相关文章
|
2月前
|
Java 开发者
解锁并发编程新姿势!深度揭秘AQS独占锁&ReentrantLock重入锁奥秘,Condition条件变量让你玩转线程协作,秒变并发大神!
【8月更文挑战第4天】AQS是Java并发编程的核心框架,为锁和同步器提供基础结构。ReentrantLock基于AQS实现可重入互斥锁,比`synchronized`更灵活,支持可中断锁获取及超时控制。通过维护计数器实现锁的重入性。Condition接口允许ReentrantLock创建多个条件变量,支持细粒度线程协作,超越了传统`wait`/`notify`机制,助力开发者构建高效可靠的并发应用。
78 0
|
2天前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
1月前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
1月前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。
|
3月前
|
存储 SQL Java
(七)全面剖析Java并发编程之线程变量副本ThreadLocal原理分析
在之前的文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析中我们曾初次谈到线程安全问题引发的"三要素":多线程、共享资源/临界资源、非原子性操作,简而言之:在同一时刻,多条线程同时对临界资源进行非原子性操作则有可能产生线程安全问题。
|
2月前
|
缓存 Java 容器
多线程环境中的虚假共享是什么?
【8月更文挑战第21天】
27 0
|
3月前
|
存储 Python 容器
Node中的AsyncLocalStorage 使用问题之在Python中,线程内变量的问题如何解决
Node中的AsyncLocalStorage 使用问题之在Python中,线程内变量的问题如何解决
|
3月前
|
存储 缓存 算法
同时使用线程本地变量以及对象缓存的问题
【7月更文挑战第15天】同时使用线程本地变量和对象缓存需小心处理以避免数据不一致、竞争条件及内存泄漏等问题。线程本地变量使各线程拥有独立存储,但若与对象缓存关联,可能导致多线程环境下访问旧数据。缺乏同步机制时,多线程并发修改缓存中的共享对象还会引起数据混乱。此外,若线程结束时未释放对象引用,可能导致内存泄漏。例如,在Web服务器场景下,若一更新缓存而另一线程仍获取旧数据,则可能返回错误信息;在图像处理应用中,若多线程无序修改算法对象则可能产生错误处理结果。因此,需确保数据一致性、避免竞争条件并妥善管理内存。
|
3月前
|
Rust 安全 程序员
Rust与C++的区别及使用问题之Rust解决多线程下的共享的问题如何解决
Rust与C++的区别及使用问题之Rust解决多线程下的共享的问题如何解决
|
4月前
|
调度
线程操作:锁、条件变量的使用
线程操作:锁、条件变量的使用
25 1

热门文章

最新文章