go sync包

news2025/1/10 3:08:33

官方文档:https://pkg.go.dev/sync

临界区

临界区(critical section)是指包含有共享数据的一段代码,这些代码可能被多个线程访问
或修改。临界区的存在就是为了保证当有一个线程在临界区内执行的时候,不能有其他任何线程被允许在临界区执行。
每个临界区都有相应的进入区(entry section)和退出区(exit section),可以按图 2.3方式表示。
在这里插入图片描述
设想有 A,B 两个线程执行同一段代码,则在任意时刻至多只能有一个线程在执行临界区内的代码。即,如果 A 线程正在临界区执行,B 线程则只能在进入区等待。只有当 A 线程执行完临界区的代码并退出临界区,原先处于等待状态的 B 线程才能继续向下执行并进入临界区。

如果只有一个主线程,输入结果正常

package main

import "fmt"

var x = 0

func main() {
    for i := 0; i < 10000; i++ {
        x = x + 1
    }
    for i := 0; i < 10000; i++ {
        x = x + 1
    }
    fmt.Printf("x value is %d\n", x)
}

输出结果:x value is 20000,符合预期

改为多协程访问,代码如下

package main

import (
    "fmt"
    "sync"
)

var x = 0
var wg sync.WaitGroup

func add() {
    for i := 0; i < 10000; i++ {
        x = x + 1
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Printf("x value is %d\n", x)
}

输入结果:x value is 12718(每次执行不一样), 不符合预期
原因分析:
x = x + 1

  1. 首先获得x的值
  2. 计算 x + 1
  3. 把步骤2的计算结果赋值给 x

假设a、b 2个协程拿到的x值都是99,当a协程进行步骤2时,b协程进行到步骤3,b协程把步骤2的值100给了x,a又重新把步骤2的值100给x,把b协程的值覆盖掉。导致x的值99经过2次加1是100而不是期望的101。

互斥锁

互斥锁(英语:Mutual exclusion,缩写 Mutex)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区域(critical section)达成。
sync.Mutex定义了两个方法:LockUnlock。所有在 Lock 和 Unlock 之间的代码,都只能由一个 Go 协程执行,于是就可以避免竞态条件。
改进后的程序如下

package main

import (
    "fmt"
    "sync"
)

var x = 0
var mutex sync.Mutex
var wg sync.WaitGroup

func add() {
    for i := 0; i < 10000; i++ {
        mutex.Lock()
        x = x + 1
        mutex.Unlock()
    }
    wg.Done()
}
func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Printf("x value is %d\n", x)
}

每次输出结果:x value is 20000,符合预期。

读写锁

读操作可并发重入,写操作是互斥的。这意味着多个线程可以同时读数据,但写数据时需要获得一个独占的锁。当写者写数据时,其它写者或读者需要等待,直到这个写者完成写操作。读写锁在Go语言中使用sync包中的RWMutex类型。

读写锁优先级策略

读写锁可以有不同的操作模式优先级:

  • 读操作优先锁:提供了最大并发性,但在锁竞争比较激烈的情况下,可能会导致写操作饥饿。这是由于只要还有一个读线程持锁,写线程就拿不到锁。多个读者可以立刻拿到锁,这意味着一个写者可能一直在等锁,期间新的读者一直可以拿到锁。极端情况下,写者线程可能会一直等锁,直到所有一开始就拿到锁的读者释放锁。读者的可以是弱优先级的,如前文所述,也可以是强优先级的,即只要写者释放锁,任何等待的读者总能先拿到。
  • 写操作优先锁:如果队列中有写者在等锁,则阻止任何新读者拿锁,来避免了写操作饥饿的问题。一旦所有已经开始的读操作完成,等待的写操作立即获得锁。和读操作优先锁相比,写操作优先锁的不足在于在写者存在的情况下并发度低。内部实现需要两把互斥锁。
  • 未指定优先级锁:不提供任何读/写的优先级保证。

Go 读写锁(sync.RWMutex)不会导致写操作饥饿。读写锁允许多个 goroutine 同时读取共享资源,但只有一个 goroutine 可以进行写操作。当有写操作等待时,读操作会被阻塞,直到写操作完成。这样可以保证写操作不会被无限期地延迟,从而避免了写操作饥饿的问题。同时,读写锁还支持优先级反转,即当有写操作等待时,新的读操作也会被阻塞,以确保写操作尽快得到执行。
读写锁优势是在读多写少的情况,举例如下:

package main

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

var x = 10
var wg sync.WaitGroup

var mutex sync.Mutex

func write() {
    mutex.Lock()
    time.Sleep(1 * time.Millisecond) // 模拟写耗时1毫秒
    x = x + 1
    mutex.Unlock()
    wg.Done()

}
func read() {
    mutex.Lock()
    time.Sleep(time.Millisecond) // 模拟读耗时1毫秒
    mutex.Unlock()
    wg.Done()

}
func main() {
    // 统计开始时间
    time1 := time.Now()
    // 开10个协程写
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }
    // 开1000个协程读
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }

    wg.Wait()
    fmt.Println("x最终值为:", x)
    // 统计结束时间
    time2 := time.Now()
    fmt.Printf("总共耗时:%v\n", time2.Sub(time1)) // 结束时间-开始时间
}

输入结果如下:
x最终值为: 20
总共耗时:15.7080693s

package main

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

var x = 10
var wg sync.WaitGroup

var rwMutex sync.RWMutex

func write() {
    rwMutex.Lock()                   // 写锁都用Lock
    time.Sleep(1 * time.Millisecond) // 模拟写耗时1毫秒
    x = x + 1
    rwMutex.Unlock()
    wg.Done()

}
func read() {
    rwMutex.RLock()              // 读锁用RLock
    time.Sleep(time.Millisecond) // 模拟读耗时1毫秒
    rwMutex.RUnlock()
    wg.Done()

}

func main() {
    // 统计开始时间
    time1 := time.Now()
    // 开10个协程写
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }
    // 开1000个协程读
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }

    wg.Wait()
    fmt.Println("x最终值为:", x)
    // 统计结束时间
    time2 := time.Now()
    fmt.Printf("总共耗时:%v\n", time2.Sub(time1)) // 结束时间-开始时间
}

输出结果如下:
x最终值为: 20
总共耗时:155.8851ms

sync.WaitGroup

请参考:https://blog.csdn.net/weixin_37909391/article/details/130853859

sync.Once

sync.Once只有一个方法,签名如下

func (o *Once) Do(f func())

当且仅当 Do 是第一次为 Once 的实例调用时,Do 才调用函数 f。
如果 once.Do(f) 被多次调用,只有第一次调用会调用 f,即使 f 在每次调用中都有不同的值。每个要执行的函数都需要一个新的 Once 实例。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    onceBody := func() {
        fmt.Println("Only once")
    }
    done := make(chan bool)
    for i := 0; i < 10; i++ {
        go func() {
            once.Do(onceBody)
            done <- true
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }
}

结果:Only once

sync.Map

方法说明
func (m *Map) CompareAndDelete(key, old any) (deleted bool)少用,略
func (m *Map) CompareAndSwap(key, old, new any) bool少用,略
func (m *Map) Delete(key any)Delete 删除键的值。
func (m *Map) Load(key any) (value any, ok bool)Load 返回存储在映射中的键值,如果不存在值则返回 nil。 ok 结果表明是否在map中找到了值。
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)LoadAndDelete 删除键的值,返回以前的值(如果有)。加载的结果报告密钥是否存在。
func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)LoadOrStore 返回键的现有值(如果存在)。否则,它存储并返回给定的值。如果值已加载,则加载结果为 true,如果已存储,则为 false。
func (m *Map) Range(f func(key, value any) bool)Range 依次为映射中存在的每个键和值调用 f。如果 f 返回 false,则 range 停止迭代。
func (m *Map) Store(key, value any)Store 设置键的值。
func (m *Map) Swap(key, value any) (previous any, loaded bool)少用,略

需要注意的是,由于 sync.Map 内部使用了一些技巧来实现并发安全,因此它的一些方法可能会比普通的 map 操作更慢。在性能要求较高的场景中,可以考虑使用其他的并发安全的数据结构,如 sync.Pool、atomic.Value 等。

package main

import (
    "fmt"
    "sync"
)

var m = make(map[int]int)

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func(n int) {
            m[n] = n * 10
            fmt.Printf("key为:%d,value为:%d\n", n, m[n])
            wg.Done()
        }(i)
    }
    wg.Wait()
    // panic:fatal error: concurrent map writes
}

package main

import (
    "fmt"
    "sync"
)

var m = sync.Map{}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 20; i++ {
        wg.Add(1)
        go func(n int) {
            m.Store(n, n*10)
            res, _ := m.Load(n)
            fmt.Printf("key为:%d,value为:%d\n", n, res)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

结果如下:
key为:0,value为:0
key为:19,value为:190
key为:5,value为:50
key为:6,value为:60
key为:7,value为:70
key为:4,value为:40
key为:8,value为:80
key为:9,value为:90
key为:10,value为:100
key为:11,value为:110
key为:3,value为:30
key为:12,value为:120
key为:15,value为:150
key为:13,value为:130
key为:18,value为:180
key为:14,value为:140
key为:17,value为:170
key为:1,value为:10
key为:16,value为:160
key为:2,value为:20

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

workquue

参考 讲解Linux内核工作队列workqueue源码分析 - 知乎 浅谈Linux内核中断下半部——工作队列&#xff08;work queue&#xff09; - 知乎 kernel/workqueue.c 初始化 /** 6004 * workqueue_init_early - early init for workqueue subsystem 6005 * 6006 * This is th…

字节测开5年经验之谈,1分钟了解自动化测试..

引子 写在最前面&#xff1a;目前自动化测试并不属于新鲜的事物&#xff0c;或者说自动化测试的各种方法论已经层出不穷&#xff0c;但是&#xff0c;能够明白自动化测试并很好落地实施的团队还不是非常多&#xff0c;我们接来下用通俗的方式来介绍自动化测试…… 本文共有2410…

WebSocket全双工通信SpringBoot实现

【IT老齐238】十分钟上手WebSocket全双工通信协议_哔哩哔哩_bilibili【IT老齐238】十分钟上手WebSocket全双工通信协议, 视频播放量 8348、弹幕量 23、点赞数 318、投硬币枚数 157、收藏人数 257、转发人数 30, 视频作者 IT老齐, 作者简介 老齐的个人V: itlaoqi001 ~~欢迎前来交…

kubernetes01

kubernetes基础 kubernetes介绍 Kubernetes是Google在2014年开源的一款容器集群系统&#xff0c;简称k8s Kubernetes用于容器化应用程序部署、扩展和管理&#xff0c;目标是让容器化应用简单高效 官方网站&#xff1a;https://kubernetes.io/ 官方文档&#xff1a;https://ku…

PFC(Priority Flow Control)及PFC Storm介绍

文章目录 PFCPFC Storm PFC PFC是一种流量控制机制&#xff0c;用于保证网络中的无损传输&#xff0c;常用于RDMA网络中&#xff0c;以下具体介绍其机制。 如图所示&#xff0c;发送方的出端口发送数据包给接收方的入端口。在发送方的出端口&#xff0c;数据包在至多八个队列中…

AI智能照片编辑:AI Photo for Mac

AI Photo是一款Mac平台上的智能照片编辑软件&#xff0c;它基于人工智能技术&#xff0c;可以帮助用户快速、轻松地对照片进行编辑和美化。AI Photo提供了多种智能修复和美化功能&#xff0c;包括自动调整色彩、对比度、亮度、清晰度等&#xff0c;使得照片的质量得到有效提升。…

二叉树:填充每个节点的下一个右侧节点指针(java)

leetcode116:填充每个节点的下一个右侧节点指针 leetcode原题链接&#xff1a;题目描述递归解法一递归方法二&#xff08;效率更高&#xff09;二叉树专题 leetcode原题链接&#xff1a; 116题&#xff1a;填充每个节点的下一个右侧节点指针 题目描述 给定一个 完美二叉树 &a…

【STL模版库】vector介绍及使用 {构造函数,迭代器,容量相关接口,增删查改;动态二维数组}

一、vector的介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它…

Shell iptales防火墙设置

文章目录 Linux 防火墙1.Linux包过滤防火墙概述2.四表五链四表五链 3.规则链之间的匹配顺序主机型防火墙网络型防火墙 4.规则链内的匹配顺序 Linux 防火墙 1.Linux包过滤防火墙概述 Linux 系统的防火墙: IP信息包过滤系统&#xff0c;它实际上由两个组件netfilter 和 iptable…

029:Mapbox GL绘制铁路黑白交替的线段

第029个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载数据显示铁路标识的那种黑白交替的线段。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共94行)相关API参考:专栏目标示例效果 配置方式 1)…

湍流的数值模拟方法概述

湍流&#xff0c;又称紊流&#xff0c;是一种极其复杂、极不规则、极不稳定的三维流动。湍流场内充满着尺度大小不同的旋涡&#xff0c;大旋涡尺度可以与整个流畅区域相当&#xff0c;而小漩涡尺度往往只有流场尺度千分之一的数量级&#xff0c;最小尺度旋涡的尺度通过其耗散掉…

蓝桥杯--挖地雷

没有白走的路&#xff0c;每一步都算数&#x1f388;&#x1f388;&#x1f388; 题目&#xff1a; 已知有很多的地窖&#xff0c;每一个地窖中又藏着很多的地雷&#xff0c;每个地窖之间都存在着相连性&#xff0c;但是不是任意的地窖都是相连的&#xff0c;要求我们找出一次能…

技术干货 | 在 PostgreSQL 中设置查询超时

在 Navicat Monitor 3 监控工具中的查询分析器画面顶部&#xff0c;我们设置了一个图表&#xff0c;用以显示等待时间最长的查询&#xff1a; 能够标识出滞后的查询非常重要&#xff0c;因为它们可以让一切陷入瘫痪。 除了在标识出慢速查询并对其进行修复外&#xff0c;另一种…

【2023 · CANN训练营第一季】昇腾AI入门课(TensorFlow)第二章——TensorFlow模型迁移训练

1.AI模型开发基础知识入门 1.1具备Python编程经验 a.使用位置和关键字参数定义和调用函数 b.字典、列表、集合 (创建、访问和迭代) c.for循环&#xff0c; for具有多个迭代器变量的循环 (例如&#xff0c;for a,b in [(1,2),(3,4)]) d.if/else条件块和条件表达式 e.字符串格式…

Echarts绘制折线图,超简单,源码点击即可运行!【文末源码地址】

文章目录 前言Apache Echarts绘制基础折线图绘制带标记的折线图绘制多条折线图绘制带标签的折线图完整源码地址 前言 本文包含的代码仅为部分片段&#xff0c;完整源码有详细注释&#xff0c;可在文末领取&#xff01; 在当今数字化时代&#xff0c;数据可视化已成为一种必不…

day12 - 图像修复

在图像处理的过程中&#xff0c;经常会遇到图像存在多余的线条或者噪声的情况&#xff0c;对于这种情况我们会先对图像进行预处理&#xff0c;去除掉对图形内容有影响的噪声&#xff0c;在进行后续的处理。 本节实验我们介绍使用图像膨胀来处理图形的多余线条&#xff0c;进行…

web前端 --- CSS(03) -- 元素定位

元素定位&#xff1a;标签在页面中的位置问题 &#xff08;1&#xff09;分类 绝对定位&#xff1a;将需要的元素直接定位固定的位置 PS&#xff1a;绝对定位&#xff0c;必须指定一个相对点&#xff08;一般是父标签&#xff09;。相对的标签必须是相对定位或者绝对定位【重…

chatgpt赋能python:PythonSearchGroup-Python搜索小组

Python Search Group - Python搜索小组 如果你是一个Python程序员&#xff0c;或者对Python编程语言感兴趣&#xff0c;那么你一定听说过Python Search Group。Python Search Group是一个专注于Python搜索和搜索引擎的小组&#xff0c;他们致力于提供高效的Python搜索工具和搜…

【Netty】字节缓冲区 ByteBuf(七)(下)

文章目录 前言一、实现原理二、ByteBuf 的使用案例三、ByteBuf 的3种使用模式3.1 堆缓冲模式3.2 直接缓冲区模式3.3 复合缓冲区模式 总结 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#xff09;Netty 架构设计&#xff08;二&#xff09;Netty Channel 概…

鸿蒙Hi3861问题解决-DevEco VSCode无法跳转

一、问题描述 使用Windows和Ubuntu混合编译下载&#xff0c;在windows下搭建VSCodeDevEco Device Tool环境。通过SSH远程Ubuntu系统进行代码修改与编译。 在VSCode中&#xff0c;Ctrl左键&#xff0c;无法跳转。按住Ctrl键&#xff0c;鼠标移到函数上&#xff0c;也不会有任何反…