首页 > 其他分享 >go语言进阶之同步原语

go语言进阶之同步原语

时间:2024-11-06 20:20:05浏览次数:3  
标签:wg 进阶 fmt goroutine sync 原语 func go main

同步原语

资源竞争

定义与实现

在Go语言中,资源竞争指多个goroutine同时访问共享资源,导致程序的行为不可预测或者不一致。

资源竞争通常发生在对同一变量进行读写操作时,如果没有正确的同步机制来控制访问可能会引发资源竞争

package main

import (
    "fmt"
    "sync"
)

var counter int // 共享资源

func increment(wg *sync.WaitGroup) {
    defer wg.Done()
    counter++
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go increment(&wg)
    }
    wg.Wait()
    fmt.Println("Final counter:", counter) // 可能不是 1000
}

![[Pasted image 20241029003944.png]]
可以运行后看到发生了资源竞争,导致不为1000。

原因

  1. 多个goroutine同时写入或读取共享资源,缺乏恰当的同步机制
  2. 非原子操作,对共享变量的操作如果不是原子的,会导致在操作过程中被其他goroutine中断,导致数据错误

与安全的关系

  1. 可能会导致数据一致性问题,影响应用数据
  2. 并发攻击,比如对ddos可以利用并发性来压垮系统,导致资源竞争和其他问题。
  3. 状态泄露,如果资源竞争导致应用状态不一样,可能可以利用这些漏洞来获取敏感信息或执行恶意操作。

同步原语

同步原语用于控制多个goroutine之间的访问顺序和协调,正好可以解决上面的资源竞争问题。

互斥锁(sync.Mutex)

确保同一时间只有一个goroutine可以访问资源

实现

package main  
  
import (  
    "fmt"  
    "sync")  
  
var (  
    mu      sync.Mutex  
    counter int  
)  
  
func increment() {  
    mu.Lock()   //加锁  
    counter++   //访问共享资源  
    mu.Unlock() //解锁  
}  
  
func main() {  
    for i := 1; i <= 1000; i++ {  
       increment()  
    }  
    fmt.Println(counter)  
  
}

可以看到现在输出为1000

![[Pasted image 20241029011834.png]]

sync.RWMutex

RWMutex是Go语言的读写互斥锁,用于处理读多写少的场景,他允许多个goroutine并发地读取共享资源,但是写操作时之允许一个goroutine进行写操作。

package main  
  
import (  
    "fmt"  
    "sync")  
  
var (  
    rwmu    sync.RWMutex // 创建读写互斥锁  
    counter int          // 共享计数器  
)  
  
func read(wg *sync.WaitGroup) {  
    defer wg.Done()                  // 完成时通知 WaitGroup    rwmu.RLock()                     // 获取读锁  
    fmt.Println("Counter:", counter) // 读取共享资源  
    rwmu.RUnlock()                   // 释放读锁  
}  
  
func write(wg *sync.WaitGroup) {  
    defer wg.Done() // 完成时通知 WaitGroup    rwmu.Lock()     // 获取写锁  
    counter++       // 修改共享资源  
    rwmu.Unlock()   // 释放写锁  
}  
  
func main() {  
    var wg sync.WaitGroup  
  
    // 启动多个读和写的 goroutine    for i := 0; i < 5; i++ {  
       wg.Add(1)  
       go read(&wg) // 启动读取 goroutine    }  
  
    for i := 0; i < 5; i++ {  
       wg.Add(1)  
       go write(&wg) // 启动写入 goroutine    }  
  
    wg.Wait() // 等待所有 goroutine 完成  
}

这个程序由于read在write前所以可以更好的体现read和write是同时进行的

输出为

![[Pasted image 20241029014147.png]]

这个的输出不固定,因为无法确定程序每一次的执行速度。

sync.WaitGroup

WaiGroup 常用于等待一组goroutine完成,它可以让主goroutine等待其他多个goroutine的完成

package main

import (
    "fmt"
    "sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 在函数结束时调用 Done()
    fmt.Printf("Worker %d is working...\n", id)
    time.Sleep(1 * time.Second) // 模拟一些工作
    fmt.Printf("Worker %d is done.\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // 增加 WaitGroup 计数
        go worker(i, &wg) // 启动 goroutine
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("All workers are done.")
}

这个函数主要的作用就是等待所有线程执行完再结束主线程,如果当主线程提前结束,其他线程就算没有完成执行操作也无法继续执行了

sync.Once

Once函数和它的名字一样,主要作用就是确保某段代码只执行一次,无论有多少个goroutine尝试调用它,都只执行一次。

package main

import (
    "fmt"
    "sync"
)

var once sync.Once

func initialize() {
    fmt.Println("正在初始化...")
}

func worker(id int) {
    // 仅调用一次初始化函数
    once.Do(initialize)
    fmt.Printf("工作者 %d 正在工作\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            worker(id)
        }(i)
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("所有工作者完成")
}

这段代码运行后可以发现,初始化操作只运行了一次,当有资源初始化等操作时,使用Once函数是一个不错的选择。

sync.Cond

Cond函数是一个用于实现条件变量的同步原语。它通常和Mutex和RWMutex一起使用,允许goroutines在特定条件下进行等待和通知

package main

import (
	"fmt"
	"sync"
	"time"
)

// Resource 结构体包含一个互斥锁和一个条件变量
type Resource struct {
	mu    sync.Mutex   // 用于保护共享资源的互斥锁
	cond  *sync.Cond   // 条件变量,用于协程之间的同步
	value int          // 共享资源的值
}

// NewResource 创建一个新的 Resource 实例
func NewResource() *Resource {
	r := &Resource{}
	r.cond = sync.NewCond(&r.mu) // 初始化条件变量,关联互斥锁
	return r
}

// SetValue 设置资源的值,并通知等待的协程
func (r *Resource) SetValue(val int) {
	r.mu.Lock()         // 获取互斥锁
	r.value = val       // 设置资源的值
	r.cond.Broadcast()  // 唤醒所有等待的协程
	r.mu.Unlock()       // 释放互斥锁
}

// GetValue 获取资源的值,如果值为0则等待
func (r *Resource) GetValue() int {
	r.mu.Lock()                       // 获取互斥锁
	for r.value == 0 {                // 如果值为0,则等待
		r.cond.Wait()                 // 等待条件变量信号
	}
	val := r.value                    // 获取资源的值
	r.mu.Unlock()                     // 释放互斥锁
	return val                        // 返回获取的值
}

func main() {
	resource := NewResource() // 创建一个新的资源实例

	// 启动一个协程,设置资源的值
	go func() {
		time.Sleep(1 * time.Second) // 等待1秒以确保 GetValue 先调用
		fmt.Println("设置值为42")
		resource.SetValue(42) // 设置值为42,并通知等待的协程
	}()

	// 主协程获取资源的值
	val := resource.GetValue() // 获取值
	fmt.Printf("获取的值是: %d\n", val) // 打印获取的值
}

这个函数主要的作用就是使用Wait方法阻塞协程,然后用Signal()或Broadcast()方法唤醒一个等待时间最长的协程;或者唤醒全部正在等待的协程。

标签:wg,进阶,fmt,goroutine,sync,原语,func,go,main
From: https://blog.csdn.net/2301_80148821/article/details/143527616

相关文章

  • CF1270 Good Bye 2019
    Dashboard玩构造玩的,服了。A拥有最大牌的必胜。linkB若相邻的差\(\ge2\)则有解,否则根据变化连续性一定无解。linkC加两个数,第一个数为之前所有数的异或和。加进来之后异或为0。第二个数为加完第一个数之后的和。linkD考虑\(k=n-1\)时,分别询问除去每个数之后的第\(......
  • 20241107,LeetCode 每日一题,使用 Go 计算两数相加
    思路模拟加法:链表存储的是逆序数位,因此从头节点开始,逐位相加可以模拟正常的加法。每两个节点的值相加,并记录进位。逐节点相加:创建一个新的链表,用于存储结果,每次将两个链表对应节点的值加上进位值,结果存储到新链表的节点中。计算过程中,将(l1.Val+l2.Val+carry)相加的结......
  • Java面向对象进阶学习一
    this关键字使用this使用的变量时属性,没有使用的是形参this可以调用结构,成员变量,方法this的理解,当前对象(在方法中调用时),或当前正在创建的对象(在构造器中调用时)针对方法内的使用情况一般情况下,我们通过对想a调用方法,可以在方法内调用当前对象a的属性或其他的方法,此时,我们可......
  • 浅析Mongodb注入
    前言不太会Mongodb的用法,这里学习一下简单介绍Mongodb是非关系型数据库(NoSQL),在MySQL中,我们所熟知的几个最常见的概念是数据库(Database)、表(Table)、字段(Column)、记录(Record)、索引(Index),这些术语映射到Mongodb中大概等价于数据库(Database)、集合(Collection)......
  • SciTech-BigDataAIML-Algorithm-Heuristic启发式- A *(Star) Algorithm(A星算法): To
    SciTech-BigDataAIML-Algorithm-Heuristic启发式A*(Star)Algorithm(A星算法):LossFunction:TotalCost="PastCost+PredicativeCost"MeasurementEuclidianDistance(欧几理得距离)\(\large\begin{array}{rl}\\EuDistance(Point_1,Point_2)&=\sqr......
  • SciTech-BigDataAIML-Algorithm-Heuristic启发式-
    SciTech-BigDataAIML-Algorithm-Heuristic启发式-LDA(LatentDirichiletAllocation)TopicsModel主题模型。LDA(LatentDirichiletAllocation,潜在狄利克雷分布)是一种TopicsModel(主题模型),用于在LargeScaleDocs(大量文档)自动发现HiddenTopics(隐藏主题)。在NLP和......
  • C# 使用mongodb 帮助类
    在C#中使用MongoDB,你需要安装MongoDB的C#驱动程序,通常使用MongoDB.Driver。以下是一个简单的帮助类,用于连接MongoDB数据库并执行基本的CRUD操作。首先,通过NuGet安装MongoDB.Driver:Install-PackageMongoDB.DriverusingMongoDB.Driver;usingSystem;usingSystem.Collectio......
  • Google play用visa虚拟信用卡下单教程
    GooglePay是简单快捷的付款服务,方便在网站和商店等结账。无论预订旅游行程、出外用餐、购买表演门票或想尝试新的体验,都不用再掏出钱包了。在网上或应用程式开始使用非常简单,只需新增付款卡,即可轻松付款。它服务包括GooglePlay图书,GooglePlay游戏,GooglePlay影视,GooglePla......
  • 基于django的宿舍考勤系统
    ......
  • 鸿蒙开发进阶(HarmonyOS )FileUri开发指南(C/C++)
     鸿蒙NEXT开发实战往期必看文章:一分钟了解”纯血版!鸿蒙HarmonyOSNext应用开发!“非常详细的”鸿蒙HarmonyOSNext应用开发学习路线!(从零基础入门到精通)HarmonyOSNEXT应用开发案例实践总结合(持续更新......)HarmonyOSNEXT应用开发性能优化实践总结(持续更新......)场景介......