Golang 并发机制-5:详解syn包同步原语

并发性是现代软件开发的一个基本方面,Go(也称为Golang)为并发编程提供了一组健壮的工具。Go语言中用于管理并发性的重要包之一是“sync”包。在本文中,我们将概述“sync”包,并深入研究其最重要的同步原语之一:Wait
Groups.

sync 包概述

sync包是Go中的一个标准库包,为并发编程提供同步原语。它为开发人员提供了协调和同步程序的工具,确保安全有序地执行并发任务。sync包提供的一些关键同步原语包括Mutexes, RWMutexes, Cond, Wait Groups。

Wait Groups

Wait Group是由Go中的“sync”包提供的同步原语。它是一个简单但功能强大的工具,用于管理goroutine的同步,特别是当你希望等待一组goroutine在继续之前完成它们的任务时。

当有多个并发执行独立任务的goroutine,并且你需要确保它们在继续执行主程序之前都已完成执行时,等待组是有用的。
在这里插入图片描述

让我们通过一个代码示例来探索如何使用Wait Groups:

package main

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

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Decrement the Wait Group counter when done
	fmt.Printf("Worker %d is working\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d has finished\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1) // Increment the Wait Group counter for each Goroutine
		go worker(i, &wg)
	}

	wg.Wait() // Wait for all Goroutines to finish
	fmt.Println("All workers have finished.")
}

在这个例子中,我们定义了一个worker函数,它通过睡眠一秒钟来模拟工作。我们启动三个goroutine,每个代表一个worker,并使用sync。来协调他们的执行。

  • wg.Add(1)在启动每个例程之前增加等待组计数器。
  • wg.Done()worker函数中被延迟,以在gooutine完成其工作时减少计数器。
  • wg.Wait()阻塞主程序,直到所有的例程都完成,确保我们等待所有工人的完成。

RWMutex

RWMutex(读写互斥)是Go语言中的一个同步原语,它允许多个线程同时读取共享数据,同时确保对写入的独占访问。它在经常读取数据但不经常修改数据的场景中非常有用。

下面是一个演示如何使用RWMutex的简单示例:

package main

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

var (
	data        int
	dataMutex   sync.RWMutex
)

func readData() int {
	dataMutex.RLock() // Read Lock
	defer dataMutex.RUnlock()
	return data
}

func writeData(value int) {
	dataMutex.Lock() // Write Lock
	defer dataMutex.Unlock()
	data = value
}

func main() {
	// Read data concurrently
	for i := 1; i <= 5; i++ {
		go func() {
			fmt.Println("Read Data:", readData())
		}()
	}

	// Write data
	writeData(42)

	time.Sleep(time.Second)
}

在这个例子中,多个Goroutine并发地读取共享的“数据”,并且一个单独的Goroutine写入它。RWMutex确保多个读取器可以同时访问数据,但一次只有一个写入器可以修改数据。

什么是条件变量?

在 Go 语言里,条件变量(sync.Cond)是一种同步原语,它用于协调多个 goroutine 的执行顺序,尤其是在某个条件满足时唤醒等待的 goroutine。条件变量通常和互斥锁(sync.Mutexsync.RWMutex)一起使用,互斥锁用于保护共享资源,而条件变量则用于在共享资源的状态发生变化时通知等待的 goroutine。

主要特性
1. 等待(Wait)

Cond.Wait() 方法会让当前 goroutine 进入等待状态,并且会自动释放与之关联的互斥锁。当其他 goroutine 调用 Cond.Signal()Cond.Broadcast() 唤醒它时,该 goroutine 会重新获取互斥锁并继续执行。

2. 单发通知(Signal)

Cond.Signal() 方法会唤醒一个正在等待该条件变量的 goroutine。如果有多个 goroutine 在等待,它会选择其中一个进行唤醒。

3. 广播通知(Broadcast)

Cond.Broadcast() 方法会唤醒所有正在等待该条件变量的 goroutine。

条件变量(Condition Variables)是同步原语,它允许程序在继续之前等待特定条件变为真。当你需要根据特定条件协调多个goroutine的执行时,它们很有帮助。

下面是一个说明条件变量使用的基本示例:

package main

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

var (
	conditionMutex sync.Mutex
	condition      *sync.Cond
	isReady        bool
)

func waitForCondition() {
	conditionMutex.Lock()
	defer conditionMutex.Unlock()

	for !isReady {
		fmt.Println("Waiting for the condition...")
		condition.Wait()
	}
	fmt.Println("Condition met, proceeding.")
}

func setCondition() {
	time.Sleep(2 * time.Second)
	conditionMutex.Lock()
	isReady = true
	condition.Signal() // Signal one waiting Goroutine
	conditionMutex.Unlock()
}

func main() {
	condition = sync.NewCond(&conditionMutex)

	go waitForCondition()
	go setCondition()

	time.Sleep(5 * time.Second)
}

在这个例子中,一个Goroutine使用condition. wait()等待条件变为真,而另一个Goroutine将条件设置为true,并使用condition. signal()向等待的Goroutine发出信号。

下面是一个简单的示例,模拟生产者 - 消费者模型,使用条件变量来协调生产者和消费者的行为:

package main

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

// 定义一个缓冲区和相关的锁与条件变量
var (
    buffer    []int
    bufferLen = 5
    mutex     sync.Mutex
    cond      = sync.NewCond(&mutex)
)

// 生产者函数
func producer(id int) {
    for {
        mutex.Lock()
        // 检查缓冲区是否已满
        for len(buffer) == bufferLen {
            fmt.Printf("Producer %d is waiting as buffer is full...\n", id)
            cond.Wait() // 缓冲区满,等待消费者消费
        }
        // 生产一个元素
        item := len(buffer) + 1
        buffer = append(buffer, item)
        fmt.Printf("Producer %d produced item %d. Buffer: %v\n", id, item, buffer)
        cond.Signal() // 通知可能正在等待的消费者
        mutex.Unlock()
        time.Sleep(time.Second)
    }
}

// 消费者函数
func consumer(id int) {
    for {
        mutex.Lock()
        // 检查缓冲区是否为空
        for len(buffer) == 0 {
            fmt.Printf("Consumer %d is waiting as buffer is empty...\n", id)
            cond.Wait() // 缓冲区空,等待生产者生产
        }
        // 消费一个元素
        item := buffer[0]
        buffer = buffer[1:]
        fmt.Printf("Consumer %d consumed item %d. Buffer: %v\n", id, item, buffer)
        cond.Signal() // 通知可能正在等待的生产者
        mutex.Unlock()
        time.Sleep(time.Second)
    }
}

func main() {
    // 启动生产者和消费者 goroutine
    go producer(1)
    go consumer(1)

    // 让程序运行一段时间
    time.Sleep(10 * time.Second)
}

通过使用条件变量,生产者和消费者能够在合适的时机进行等待和唤醒,确保缓冲区不会溢出或空消费。

原子操作

原子操作(Atomic Operations )是作为单个、不可分割的工作单元执行的操作。它们通常用于在不需要互斥锁的情况下安全地更新并发程序中的共享变量。Go为原子操作提供了一个名为“atomic”的包。

下面是演示原子操作的例子:

package main

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

var (
	counter int32
	wg      sync.WaitGroup
)

func incrementCounter() {
	defer wg.Done()
	for i := 0; i < 100000; i++ {
		atomic.AddInt32(&counter, 1)
	}
}

func main() {
	wg.Add(2)
	go incrementCounter()
	go incrementCounter()
	wg.Wait()

	fmt.Println("Counter:", atomic.LoadInt32(&counter))
}

在这个例子中,两个线程使用原子操作增加共享的“计数器”变量。atomic.AddInt32函数确保增量操作是原子性的,并且对于并发访问是安全的。

选择正确的同步机制

在选择正确的同步机制时,请考虑以下指导原则:

  • 当需要细粒度的访问控制时,互斥锁(RWMutex用于读,Mutex用于写)适用于保护共享数据。
  • 当需要根据特定条件协调程序时,条件变量很有价值。
  • 当希望避免互斥锁的开销时,原子操作对于对共享变量进行简单操作是有效的。
  • 始终选择最适合您特定用例需求的同步机制。

总之,Go在“sync”包和原子操作中提供了一套通用的同步机制,用于管理对共享资源的并发访问。了解这些工具并根据您的并发需求选择合适的工具对于编写高效可靠的并发Go程序至关重要。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PyQt】超级超级笨的pyqt计算器案例

计算器 1.QT Designer设计外观 1.pushButton2.textEdit3.groupBox4.布局设计 2.加载ui文件 导入模块&#xff1a; sys&#xff1a;用于处理命令行参数。 QApplication&#xff1a;PyQt5 应用程序类。 QWidget&#xff1a;窗口基类。 uic&#xff1a;用于加载 .ui 文件。…

Flutter Scaffold 页面结构

Material是一套设计风格&#xff0c;提供了大量的小部件&#xff0c;这里用Material风格搭建一个常见的应用页面结构。 创建Material应用 import package:flutter/material.dart;class App extends StatelessWidget {overrideWidget build(BuildContext context) {return Mat…

2月3日星期一今日早报简报微语报早读

2月3日星期一&#xff0c;农历正月初六&#xff0c;早报#微语早读。 1、多个景区发布公告&#xff1a;售票数量已达上限&#xff0c;请游客合理安排行程&#xff1b; 2、2025春节档总票房破70亿&#xff0c;《哪吒之魔童闹海》破31亿&#xff1b; 3、美宣布对中国商品加征10…

大模型本地化部署(Ollama + Open-WebUI)

文章目录 环境准备下载Ollama模型下载下载Open-WebUI 本地化部署的Web图形化界面本地模型联网查询安装 Docker安装 SearXNG本地模型联网查询 环境准备 下载Ollama 下载地址&#xff1a;Ollama网址 安装完成后&#xff0c;命令行里执行命令 ollama -v查看是否安装成功。安装成…

10 Flink CDC

10 Flink CDC 1. CDC是什么2. CDC 的种类3. 传统CDC与Flink CDC对比4. Flink-CDC 案例5. Flink SQL 方式的案例 1. CDC是什么 CDC 是 Change Data Capture&#xff08;变更数据获取&#xff09;的简称。核心思想是&#xff0c;监测并捕获数据库的变动&#xff08;包括数据或数…

【25考研】南开软件考研复试复习重点!

一、复试内容 复试采取现场复试的方式。复试分为笔试、机试和面试三部分。三部分合计100分&#xff0c;其中笔试成绩占30%、机试成绩占30%、面试成绩占40%。 1.笔试&#xff1a;专业综合基础测试 考核方式&#xff1a;闭卷考试&#xff0c;时长为90分钟。 笔试考查内容范围…

【Cadence仿真技巧学习笔记】求解65nm库晶体管参数un, e0, Cox

在设计放大器的第一步就是确定好晶体管参数和直流工作点的选取。通过阅读文献&#xff0c;我了解到L波段低噪声放大器的mos器件最优宽度计算公式为 W o p t . p 3 2 1 ω L C o x R s Q s p W_{opt.p}\frac{3}{2}\frac{1}{\omega LC_{ox}R_{s}Q_{sp}} Wopt.p​23​ωLCox​Rs…

【leetcode练习·二叉树拓展】归并排序详解及应用

本文参考labuladong算法笔记[拓展&#xff1a;归并排序详解及应用 | labuladong 的算法笔记] “归并排序就是二叉树的后序遍历”——labuladong 就说归并排序吧&#xff0c;如果给你看代码&#xff0c;让你脑补一下归并排序的过程&#xff0c;你脑子里会出现什么场景&#xff…

[ESP32:Vscode+PlatformIO]新建工程 常用配置与设置

2025-1-29 一、新建工程 选择一个要创建工程文件夹的地方&#xff0c;在空白处鼠标右键选择通过Code打开 打开Vscode&#xff0c;点击platformIO图标&#xff0c;选择PIO Home下的open&#xff0c;最后点击new project 按照下图进行设置 第一个是工程文件夹的名称 第二个是…

Docker 部署教程jenkins

Docker 部署 jenkins 教程 Jenkins 官方网站 Jenkins 是一个开源的自动化服务器&#xff0c;主要用于持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;过程。它帮助开发人员自动化构建、测试和部署应用程序&#xff0c;显著提高软件开发的效率和质量…

MySQL锁类型(详解)

锁的分类图&#xff0c;如下&#xff1a; 锁操作类型划分 读锁 : 也称为共享锁 、英文用S表示。针对同一份数据&#xff0c;多个事务的读操作可以同时进行而不会互相影响&#xff0c;相互不阻塞的。 写锁 : 也称为排他锁 、英文用X表示。当前写操作没有完成前&#xff0c;它会…

《苍穹外卖》项目学习记录-Day11销量排名统计

销量排名需要查两张表&#xff0c;一张是order_detail&#xff0c;它里面有number字段&#xff0c;这个字段体现了商品的销售的份数。我们仅仅查这一张表是不够的&#xff0c;因为用户下单了&#xff0c;下单了之后就会产生订单数据和对应的订单详情数据&#xff0c;假设他下完…

走向基于大语言模型的新一代推荐系统:综述与展望

HightLight 论文题目&#xff1a;Towards Next-Generation LLM-based Recommender Systems: A Survey and Beyond作者机构&#xff1a;吉林大学、香港理工大学、悉尼科技大学、Meta AI论文地址&#xff1a; https://arxiv.org/abs/2410.1974 基于大语言模型的下一代推荐系统&…

Vue3学习笔记-模板语法和属性绑定-2

一、文本插值 使用{ {val}}放入变量&#xff0c;在JS代码中可以设置变量的值 <template><p>{{msg}}</p> </template> <script> export default {data(){return {msg: 文本插值}} } </script> 文本值可以是字符串&#xff0c;可以是布尔…

python算法和数据结构刷题[5]:动态规划

动态规划&#xff08;Dynamic Programming, DP&#xff09;是一种算法思想&#xff0c;用于解决具有最优子结构的问题。它通过将大问题分解为小问题&#xff0c;并找到这些小问题的最优解&#xff0c;从而得到整个问题的最优解。动态规划与分治法相似&#xff0c;但区别在于动态…

【阅读笔记】New Edge Diected Interpolation,NEDI算法,待续

一、概述 由Li等提出的新的边缘指导插值(New Edge—Di-ected Interpolation&#xff0c;NEDI)算法是一种具有良好边缘保持效果的新算法&#xff0c;它利用低分辨率图像与高分辨率图像的局部协方差问的几何对偶性来对高分辨率图像进行自适应插值。 2001年Xin Li和M.T. Orchard…

C++ Primer 迭代器

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

DeepSeek R1 简易指南:架构、本地部署和硬件要求

DeepSeek 团队近期发布的DeepSeek-R1技术论文展示了其在增强大语言模型推理能力方面的创新实践。该研究突破性地采用强化学习&#xff08;Reinforcement Learning&#xff09;作为核心训练范式&#xff0c;在不依赖大规模监督微调的前提下显著提升了模型的复杂问题求解能力。 技…

直方图:摄影中的视觉数据指南

目录 一、直方图基础&#xff1a;揭开它的神秘面纱 二、解读直方图类型&#xff1a;亮度与色彩的密码 &#xff08;一&#xff09;亮度直方图 &#xff08;二&#xff09;RGB 直方图 三、拍摄中巧用直方图&#xff1a;优化曝光与效果 &#xff08;一&#xff09;精准判断曝…

力扣动态规划-19【算法学习day.113】

前言 ###我做这类文章一个重要的目的还是记录自己的学习过程&#xff0c;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非常非常高滴&#xff01;&#xff01;&#xff01; 习题 1.矩形中移动的最大次数 题目链接…