首页 > 其他分享 >为啥 两个线程同时执行 ++i,可能会导致其中一个线程的自增操作被覆盖

为啥 两个线程同时执行 ++i,可能会导致其中一个线程的自增操作被覆盖

时间:2024-07-08 16:54:04浏览次数:8  
标签:wg 自增 ++ sync 线程 var 操作

这是一个典型的并发编程问题,涉及到线程的同步和共享资源的访问问题。让我们详细来看一下为什么会这样:

自增操作的细节


++i 看起来是一个简单的操作,但实际上它包含了多个步骤:
  1. 读取变量 i 的当前值。
  2. 将读取到的值加 1。
  3. 将结果写回变量 i

在单线程环境中,这些步骤会顺序执行,不会有问题。

多线程环境中的问题


当两个线程同时执行 ++i 操作时,以下情况可能发生:
  1. 线程A 读取 i 的当前值,比如 i=5
  2. 线程B 也读取 i 的当前值,仍然是 i=5
  3. 线程A 将 i 加 1,结果是 6,并写回 i
  4. 线程B 也将读取到的 i 加 1,结果是 6,并写回 i

最终,虽然两个线程都执行了自增操作,但结果却只增加了 1,而不是预期的 2,这就是所谓的“覆盖”问题。

解决方法


要解决这个问题,可以使用同步机制来确保每次只有一个线程执行 ++i 操作。以下是一些常见的方法:
  1. 使用互斥锁(Mutex):在执行 ++i 操作时,加锁保证只有一个线程能访问这个操作。

java   Copy
   synchronized (this) {
       ++i;
   }

  1. 使用原子操作(Atomic Operations):例如在Java中可以使用 AtomicInteger,它提供了一些线程安全的操作。

java   Copy
   AtomicInteger atomicInteger = new AtomicInteger();
   atomicInteger.incrementAndGet();

  1. 使用其他高层次的并发控制机制:如信号量(Semaphore)、读写锁(ReadWriteLock)等。

通过这些方法,可以确保自增操作在多线程环境下的正确性,避免覆盖问题。       在 Go 语言中,也存在类似的问题,当多个 Goroutine 同时访问和修改共享变量时,会出现竞态条件(Race Condition)。为了避免这种情况,可以使用以下几种方法:

方法一:使用互斥锁(Mutex)


使用 sync 包中的 Mutex 来保护共享变量的访问。
go   Copy
package main

import (
    "fmt"
    "sync"
)

func main() {
    var i int
    var mu sync.Mutex
    var wg sync.WaitGroup

    for j := 0; j < 1000; j++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            i++
            mu.Unlock()
        }()
    }

    wg.Wait()
    fmt.Println("Final value of i:", i)
}

在这个例子中,mu.Lock() 和 mu.Unlock() 确保了每次只有一个 Goroutine 能执行 i++ 操作。

方法二:使用原子操作(Atomic Operations)


使用 sync/atomic 包提供的原子操作,可以在不使用锁的情况下实现线程安全。
go   Copy
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {
    var i int32
    var wg sync.WaitGroup

    for j := 0; j < 1000; j++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddInt32(&i, 1)
        }()
    }

    wg.Wait()
    fmt.Println("Final value of i:", i)
}

在这个例子中,atomic.AddInt32(&i, 1) 确保了 i 的自增操作是原子的,不会被其他 Goroutine 干扰。

方法三:使用通道(Channels)


Go 提供了通道(Channels),可以用于在 Goroutine 之间传递数据,避免共享内存的直接修改。
go   Copy
package main

import (
    "fmt"
    "sync"
)

func main() {
    var i int
    var wg sync.WaitGroup
    ch := make(chan bool)

    for j := 0; j < 1000; j++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            ch <- true
        }()
    }

    go func() {
        for range ch {
            i++
        }
    }()

    wg.Wait()
    close(ch) // 关闭通道,确保所有数据都被处理
    fmt.Println("Final value of i:", i)
}

在这个例子中,通过通道 ch 来传递信号,每当有一个 Goroutine 完成时,i 就会增加 1。

总结


在 Go 中,避免竞态条件的常见方法包括:
  1. 使用互斥锁(Mutex)来保护共享变量。
  2. 使用原子操作(Atomic Operations)来进行原子性的增减操作。
  3. 使用通道(Channels)来传递数据,避免直接共享内存。

根据具体的需求和情况选择合适的方法,可以有效避免竞态条件问题。

标签:wg,自增,++,sync,线程,var,操作
From: https://www.cnblogs.com/gongxianjin/p/18290309

相关文章

  • 力扣常用c++操作
    数字转字符串to_string()自定义sort函数sort(intervals.begin(),intervals.end(),[](vector<int>&v1,vector<int>&v2){returnv1[0]<v2[0];});自定义二分查找autoinsertit=lower_bound(intervals.begin(),intervals.end(),newInterval[0],......
  • 《框架封装 · 线程装饰器》
    ......
  • C++基础
    面向对象开发具有4个特性:封装(Encapsulation):封装是将数据和方法组合在一起,对外部隐藏实现细节,只公开对外提供的接口。这样可以提高安全性、可靠性和灵活性。继承(Inheritance):继承是从已有类中派生出新类,新类具有已有类的属性和方法,并且可以扩展或修改这些属性和方法。这样可以提......
  • C/C++ 断言 assert 的使用方法和注意事项
    C/C++中的断言(Assertion)是一种调试辅助工具,主要用于在开发过程中检测程序中的错误。断言对于确保程序的内部状态满足特定条件非常有用。如果条件为真(即,预期的条件得到了满足),程序可以继续执行。如果条件为假,则断言失败,程序会报告错误并终止执行。使用方法在C语言中,断言是通过a......
  • CCF-GESP计算机学会等级考试2024年6月六级C++T2二叉树
    解析:详见代码:#include<bits/stdc++.h>usingnamespacestd;intn;intq;strings;intp[100005];//p[i]表示i的父节点inta[100005];//对第i个节点的操作次数intb[100005];//第i个节点变化的总次数intdfs(intx){if(b[x]>0)returnb[x];//如果已计算,直接返......
  • CCF-GESP计算机学会等级考试2024年6月五级C++T2小杨的幸运数字
    解析:对每个数分解质因数,并统计质因数个数,详见代码:#include<bits/stdc++.h>usingnamespacestd;intn;intmain(){cin>>n;for(inti=1;i<=n;i++){intx;cin>>x;intcnt=0;//质因数个数for(intj=2;j*j......
  • CCF-GESP计算机学会等级考试2024年6月五级C++T1黑白格
    解析: 先把每行做前缀和(方便求区间和),枚举开始列和结束列,按行做双指针求和,找到和大于等于k的最小矩阵,时间复杂度O(N^3)。#include<bits/stdc++.h>usingnamespacestd;intm,n,k;inta[105][105];intans=1e9;intmain(){cin>>n>>m>>k;for(inti=1;i<=n;i++......
  • 0算法基础——深度优先搜索(c++)
            搜索是对一个事物的查询。他可以给出两点最短路,还能求方案数等等。好的,正文开始:深度优先搜索    深度优先搜索(dfs)顾名思义就是从深度的角度出发进行搜索。具体来讲,就是完成一个步骤后将它的每一个子步骤都试一遍,注意是先搜完子步骤(一般认为子步骤层......
  • C++ Boost 字符串处理库
    Boost库是一个由C/C++语言的开发者创建并更新维护的开源类库,其提供了许多功能强大的程序库和工具,用于开发高质量、可移植、高效的C应用程序。Boost库可以作为标准C库的后备,通常被称为准标准库,是C标准化进程的重要开发引擎之一。使用Boost库可以加速C应用程序的开发过程,提高代码质......
  • SpringBoot使用线程池实现异步批量处理任务
    模拟批处理大量数据@Slf4j@ComponentpublicclassTestFutureService{@AutowiredprivateTestFutureServiceImpltestFutureServiceImpl;/***多线程的优势:多核CPU使用多线程可以提高CPU的利用率(单核CPU不行,反而降低),可以实现异步调用。**......