首页 > 其他分享 >go 语言学习之 mutex

go 语言学习之 mutex

时间:2025-01-11 19:32:23浏览次数:1  
标签:wg Account 语言 竞争 amount 线程 go mutex balance

竞争条件和数据竞争是并发编程中常见的两个概念,它们都涉及到多个线程或进程对共享数据的并发访问,但具体定义和应用场景有所不同。

竞争条件(Race Condition)

定义:竞争条件是指多个线程或进程在读写一个共享数据时,其最终结果依赖于它们执行的相对时间的情形。也就是说,当事件的时序影响一段代码的正确性时,就会发生竞争条件。

示例:假设两个进程P1和P2共享变量a。在某一执行时刻,P1更新a为1,在另一时刻,P2更新a为2。因此两个任务竞争地写变量a,最终变量a的值取决于哪个进程最后执行写操作。

解决方法

  1. 互斥访问:通过使用互斥锁或信号量等机制,确保在任意时刻只有一个线程可以访问共享资源。
  2. 同步访问:通过使用条件变量或事件等机制,确保线程之间的协调和通信。
  3. 数据副本:对于只读的共享资源,可以为每个线程创建一个副本,使每个线程都有自己的私有数据,从而避免竞争条件。
  4. 原子操作:使用原子操作来执行对共享资源的读取和写入操作,这样就能保证这些操作是不可中断的,从而避免竞争条件。
  5. 使用线程安全的数据结构:选择使用线程安全的数据结构,这些数据结构在内部已经实现了对共享资源的同步和互斥访问。
  6. 避免共享资源:尽可能地避免多个线程同时访问和修改共享资源的情况,可以通过设计合理的程序结构和减少共享资源的使用来避免竞争条件。

数据竞争(Data Race)

定义:数据竞争是指多个线程或进程同时访问共享数据,其中至少一个线程或进程在进行写操作,而这些访问没有进行适当的同步。数据竞争的结果通常是未定义的行为,可能导致程序崩溃或输出错误的结果。

特征

  • 并发访问:多个线程或进程同时访问共享数据。
  • 不一致的状态:数据在不同线程中可能处于不同的状态。
  • 不可预测的结果:程序的行为可能因为数据竞争而不可预测。

示例:考虑一个银行转账的场景,两个线程同时对同一个账户进行读写操作,但没有适当的同步机制,导致最终账户余额可能不一致。

解决方法

  1. 使用同步机制:通过使用互斥锁(Mutex)、读写锁(Read/Write Lock)等同步工具来保护共享数据。
  2. 设计良好的并发模型:使用高层次的并发库,以避免手动管理锁和同步,降低数据竞争的风险。
  3. 定期进行代码审查:通过定期的代码审查和测试,识别并修复潜在的并发问题。确保代码遵循最佳实践,并且使用了适当的同步机制。
  4. 使用原子操作:使用原子操作来执行对共享资源的读取和写入操作,这样就能保证这些操作是不可中断的,从而避免数据竞争。

区别

  • 定义:竞争条件是指多个线程或进程在读写共享数据时,其最终结果依赖于它们执行的相对时间的情形。数据竞争是指多个线程或进程同时访问共享数据,其中至少一个线程或进程在进行写操作,而这些访问没有进行适当的同步。
  • 影响:竞争条件可能导致程序行为不确定,但不一定导致未定义行为。数据竞争通常会导致未定义行为,可能导致程序崩溃或数据损坏。
  • 检测:数据竞争可以通过 -race 来检测。竞争条件的检测通常需要更复杂的时序分析和测试。

通过理解这两个概念的区别,可以更好地设计和实现并发程序,避免潜在的错误和性能问题。

原子操作

package main

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

type Account struct {
	balance int64
}

func (a *Account) Deposit(amount int64) {
	atomic.AddInt64(&a.balance, amount)
}

func (a *Account) Withdraw(amount int64) bool {
	for {
		currentBalance := atomic.LoadInt64(&a.balance)
		if currentBalance < amount {
			return false
		}
		newBalance := currentBalance - amount
		// 使用 CompareAndSwapInt64 确保原子性更新
		if atomic.CompareAndSwapInt64(&a.balance, currentBalance, newBalance) {
			return true
		}
		// 如果 CAS 失败,说明有其他 goroutine 修改了余额,重新尝试
	}
}

func (a *Account) Balance() int64 {
	return atomic.LoadInt64(&a.balance)
}

func transfer(from, to *Account, amount int64, wg *sync.WaitGroup) {
	defer wg.Done()
	if from.Withdraw(amount) {
		to.Deposit(amount)
		fmt.Println("Transfer successful")
	} else {
		fmt.Println("Insufficient funds")
	}
}

func main() {
	account1 := &Account{balance: 10000}
	account2 := &Account{balance: 5800}
	var wg sync.WaitGroup

	// 模拟多个并发转账
	for i := 0; i < 98; i++ {
		wg.Add(1)
		go transfer(account1, account2, 100, &wg)
	}
	wg.Wait()

	fmt.Println("Final balance of account1:", account1.Balance())
	fmt.Println("Final balance of account2:", account2.Balance())
}`


使用mutex
`package main

import (
	"fmt"
	"sync"
)

type Account struct {
	balance int64
	mu      sync.Mutex
}

func (a *Account) Deposit(amount int64) {
	a.mu.Lock()
	defer a.mu.Unlock()
	a.balance += amount
}

func (a *Account) Withdraw(amount int64) bool {
	a.mu.Lock()
	defer a.mu.Unlock()
	if a.balance < amount {
		return false
	}
	a.balance -= amount
	return true
}

func (a *Account) Balance() int64 {
	a.mu.Lock()
	defer a.mu.Unlock()
	return a.balance
}

func transfer(from, to *Account, amount int64, wg *sync.WaitGroup) {
	defer wg.Done()
	if from.Withdraw(amount) {
		to.Deposit(amount)
		fmt.Println("Transfer successful")
	} else {
		fmt.Println("Insufficient funds")
	}
}

func main() {
	account1 := &Account{balance: 10000}
	account2 := &Account{balance: 5800}
	var wg sync.WaitGroup

	// 模拟多个并发转账
	for i := 0; i < 98; i++ {
		wg.Add(1)
		go transfer(account1, account2, 100, &wg)
	}
	wg.Wait()

	fmt.Println("Final balance of account1:", account1.Balance())
	fmt.Println("Final balance of account2:", account2.Balance())
}`

标签:wg,Account,语言,竞争,amount,线程,go,mutex,balance
From: https://www.cnblogs.com/timevalue/p/18666127

相关文章

  • Google AI 智能体白皮书,超详细解读(内附下载)
    2AGI.NET|探索AI无限潜力,2AGI为您带来最前沿资讯。扫码加入2AGI技术社区!本文深入探讨了生成式AI智能体的核心组件、工作原理、关键技术及其广泛应用。文章从智能体的定义出发,详细介绍了其模型、工具和编排层的协同作用,以及认知架构的运作机制。同时,文章还讨论了如......
  • 使用 MongoDB 和 OpenAI 实现 RAG 的实战指南
    在本篇文章中,我们将深入探讨如何使用MongoDB和OpenAI实现检索增强生成(RAG,Retrieve-AugmentedGeneration)。通过结合数据库的高效检索能力和语言模型的生成能力,可以创建出功能强大的应用。接下来,我们将详细介绍如何搭建这样的系统,并提供可运行的代码示例。技术背景介绍......
  • 用C语言写一个简单的五子棋游戏,新手刚上路做的第一个小项目
    1/*23451.制作15*15的棋盘,放到二维数组中,制作成一个函数;62.for循环,输入坐标,结构分两部分,第一部分为甲,第二部分为乙7(1)根据坐标,指向棋盘的某个位置,更改常量,并刷新棋盘8(2)判断是否有重复下子,有刷新棋盘,并提示重新下子9(3)根据输入的坐标,判断横竖......
  • 指针(全解)—C语言进阶
    目录字符指针例题指针数组(数组)数组指针再次讨论数组名数组指针来源理解练习:数组传参4.数组参数和指针参数4.1 一维数组传参4.2二维数组传参4.3 一级指针传参4.4 二级指针传参5.函数指针例子函数指针的用法函数指针数组函数指针数组的用法:指向函数指......
  • Anaconda Go2 EDU ubuntu
     (base)bim@bim-Smart-Client:~/Anaconda$(base)bim@bim-Smart-Client:~/Anaconda$(base)bim@bim-Smart-Client:~/Anaconda$condacreate-nwind_2025python=3.8 Channels:-defaultsPlatform:linux-64Collectingpackagemetadata(repodata.json):don......
  • 【Django】自定义中间件的注册和使用,利用反射规范代码
    在Django中创建和使用自定义中间件1.创建自定义中间件在你的Django应用目录下创建一个新的文件,通常命名为middleware.py。在这个文件中,你可以定义自己的中间件类。示例中间件#middleware.pyclassCustomMiddleware:def__init__(self,get_response):......
  • 使用自然语言查询 Supabase 的实践指南
    在本篇文章中,我们将介绍如何使用自然语言结构化查询Supabase数据库。Supabase是一个开源的Firebase替代品,构建在PostgreSQL之上。通过使用pgvector来存储嵌入向量,可以使得数据库的查询更为智能和高效。技术背景介绍Supabase提供了一种现代化的数据库管理方式,通......
  • P1433 吃奶酪(C语言)
    题目描述房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)点处。输入格式第一行有一个整数,表示奶酪的数量 nn。第 2 到第(n+1) 行,每行两个实数,第 (i+1)(i+1) 行的实数分别表示第 ii 块奶酪的横纵坐标 xi,yi。输出格式......
  • HKUST:通过跨语言触发LLM后门攻击
    ......
  • Golang——rune和byte
    本文详细介绍Golang中的两种字符类型rune和byte,介绍他们的区别,编码方式和简单的使用。文章目录`byte`类型`rune`类型UTF-8与Unicode的关系byte和rune的主要区别Go的默认编码方式遍历方式遍历`byte`遍历`rune`补充字符还原从`byte`序列还原字符串从`rune`......