sync

简介: sync包有以下几个内容:(1)sync.Pool 临时对象池(2)sync.Mutex 互斥锁(3)sync.RWMutex 读写互斥锁(4)sync.WaitGroup 组等待(5)sync.Cond 条件等待(6)sync.Once 单次执行一、临时对象池Pool可以用来存储临时对象,其实原理就是这个对象池指向对象变量,以防没有变量指向对象时,被GC所回收。

sync包有以下几个内容:
(1)sync.Pool 临时对象池
(2)sync.Mutex 互斥锁
(3)sync.RWMutex 读写互斥锁
(4)sync.WaitGroup 组等待
(5)sync.Cond 条件等待
(6)sync.Once 单次执行

一、临时对象池
Pool可以用来存储临时对象,其实原理就是这个对象池指向对象变量,以防没有变量指向对象时,被GC所回收。其目的时为了避免重复创建相同的对象造成GC的负担,其中存放的临时对象随时可能被GC回收掉(如果该对象不再被其它变量引用)。
从Pool中取出对象时,如果Pool中没有对象,将回nil,但是如果给Pool。New字段指定一个函数的话,Pool将使用函数创建一个新对象返回。
Pool可以安全的在多个协程中并行使用,但Pool并不适用于所有空闲对象,Pool应该用来管理并发的协程共享的临时对象,而不应该管理短寿命对象中的临时对象,因为这种情况下内存不能很好的分配,这些短寿命对象应该自己实现空闲列表。

type Pool struct {
        // 创建临时对象的函数
        New func() interface{}
}

// 向临时对象池中存入对象
func (p *Pool) Put() x interface{}

// 向临时对象池中取出对象
func (p *Pool) Get() interface{}

案例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var pool sync.Pool
    var val interface{}
    pool.Put("1")
    pool.Put(12)
    pool.Put(true)

    for {
        val = pool.Get()
        if val == nil {
            break
        }
        fmt.Println(val)
    }
}

二、互斥锁
互斥锁用来保证再任一时刻,只能有一个协程访问某对象。Mutex的初始值为解锁状态,Mutex通常作为其它结构体的匿名字段使用,使该结构体具有Lock和Unlock方法。
Mutex可以安全的再多个协程中并行使用。

注意:如果对未加锁的进行解锁,则会引发panic。

加锁,保证数据能够正确:

package main

import (
    "fmt"
    "sync"
)

var num int
var mu sync.Mutex
var wg sync.WaitGroup

func main() {
    wg.Add(10000)
    for i := 0; i < 10000; i++ {
        go Add()
    }
    wg.Wait()
    fmt.Println(num)
}

func Add() {
    mu.Lock() // 加锁
    defer func() {
        mu.Unlock() // 解锁
        wg.Done()
    }()

    num++
}

并发没有加锁,如下代码,当你执行下面代码时,会发现结果不等于10000,其原因是因为出现这样的情况,就是其中一些协程刚好读取了num的值,此时该协程刚好时间片结束,被挂起,没有完成加1。然后其他协程进行加1,然后当调度回到原来那个协程,num = 原来的那个num(而不是最新的num) + 1,导致num数据出错:

package main

import (
    "fmt"
    "sync"
)

var num int
var wg sync.WaitGroup

func main() {
    wg.Add(10000)
    for i := 0; i < 10000; i++ {
        go Add()
    }
    wg.Wait()
    fmt.Println(num)
}

func Add() {
    defer wg.Done()
    num++
}

三、读写互斥锁
RWMutex比Mutex多了一个“写锁定” 和 “读锁定”,可以让多个协程同时读取某对象。RWMutex的初始值为解锁状态。RWMutex通常作为其它结构体的匿名字段使用。
RWMutex可以安全的在多个协程中并行使用。

// Lock 将 rw 设置为写状态,禁止其他协程读取或写入
func (rw *RWMutex) Lock()

// Unlock 解除 rw 的写锁定状态,如果rw未被锁定,则该操作会引发 panic。
func (rw *RWMutex) Unlock()

// RLock 将 rw 设置为锁定状态,禁止其他协程写入,但可以读取。
func (rw *RWMutex) RLock()

// Runlock 解除 rw 设置为读锁定状态,如果rw未被锁定,则该操作会引发 panic。
func (rw *RWMutex) RUnLock()

// RLocker 返回一个互斥锁,将 rw.RLock 和 rw.RUnlock 封装成一个 Locker 接口。
func (rw *RWMutex) RLocker() Locker

四、组等待
WaitGroup 用于等待一组协程的结束。主协程在创建每个子协程的时候先调用Add增加等待计数,每个子例程在结束时调用 Done 减少例程计数。之后,主协程通过 Wait 方法开始等待,直到计数器归零才继续执行。

// 计数器增加 delta,delte可以时负数
func (wg *WaitGroup) Add(delta int)

// 计数器减少1,等价于Add(-1)
func (wg *WaitGroup) Done()

// 等待直到计数器归零。如果计数器小于0,则该操作会引发 panic。
func (wg *WaitGroup) Wait()

五、条件等待
条件等待和互斥锁有不同,互斥锁是不同协程公用一个锁,条件等待是不同协程各用一个锁,但是wait()方法调用会等待(阻塞),直到有信号发过来,不同协程是共用信号

package main

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

func main() {
    var wg sync.WaitGroup
    cond := sync.NewCond(new(sync.Mutex))

    for i := 0; i < 3; i++ {
        go func(i int) {
            fmt.Println("协程", i, "启动。。。")
            wg.Add(1)
            defer wg.Done()
            cond.L.Lock()
            fmt.Println("协程", i, "加锁。。。")
            cond.Wait()
            fmt.Println("协程", i, "解锁。。。")
            cond.L.Unlock()
        }(i)
    }
    time.Sleep(2e9)
    cond.L.Lock()
    fmt.Println("主协程发送信号量。。。")
    cond.Signal()
    cond.L.Unlock()

    time.Sleep(2e9)
    cond.L.Lock()
    fmt.Println("主协程发送信号量。。。")
    cond.Signal()
    cond.L.Unlock()

    time.Sleep(2e9)
    cond.L.Lock()
    fmt.Println("主协程发送信号量。。。")
    cond.Signal()
    cond.L.Unlock()
    wg.Wait()
}

六、单次执行
Once的作用是多次调用但只执行一次,Once只有一个方法,Once.Do(),向Do传入一个函数,这个函数在第一次执行Once.Do()的时候会被调用,以后再执行Once.Do()将没有任何动作,即使传入了其他的函数,也不会被执行,如果要执行其它函数,需要重新创建一个Once对象。
Once可以安全的再多个协程中并行使用。是协程安全的

标准库中原型:
// 多次调用仅执行一次指定的函数f
func (o *Once) Do(f func())

// 示例:Once
package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var wg sync.WaitGroup

    onceFunc := func() {
        fmt.Println("hello")
    }
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func() {
            defer wg.Done()
            once.Do(onceFunc) // 多次调用只执行一次
        }()
    }
    wg.Wait()
}

参考:
(1)https://www.cnblogs.com/golove/p/5918082.html
(2)https://blog.csdn.net/wangshubo1989/article/details/77966432?locationNum=9&fps=1

目录
相关文章
|
9月前
|
供应链 搜索推荐 数据可视化
《电商管理:精准营销与客户忠诚度培育》
本文概述了电商管理的重要性和关键领域,包括供应链管理、客户关系管理、数据管理和营销推广管理。文章指出,有效的电商管理能帮助企业应对激烈的市场竞争、多变的消费者需求和技术快速更新等挑战,实现高效运营和可持续发展。特别介绍了“板栗看板”这一创新工具,强调其在提升电商管理效率方面的显著作用。
《电商管理:精准营销与客户忠诚度培育》
|
流计算 计算机视觉 索引
使用ffmpeg将视频转成HLS(m3u8)格式
HLS (HTTP Live Streaming)是苹果推出的视频流协议,HLS格式的视频包含一个m3u8文本文件,以及众多的.ts的视频片段,而m3u8文本文件的作用就是将这些ts片段索引起来。 因为HLS协议是将视频切分成很多小的ts片段,这些小片段很适合放到cdn上,有很多视频文章都使用了hls格式传输视频。今天我在这里教大家如何用ffmpeg将mp4格式的视频转为HLS(m3u8)格式。
1073 0
|
安全 Linux 虚拟化
0基础教你安装VM 17PRO-直接就是专业许可证版
0基础教你安装VM 17PRO-直接就是专业许可证版
|
弹性计算 固态存储 数据可视化
阿里云服务器租用费用
2023年阿里云服务器租用费用,阿里云轻量应用服务器2核2G3M带宽轻量服务器一年108元,2核4G4M带宽轻量服务器一年297.98元12个月
333 0
|
SQL 数据挖掘 BI
SSIS中的容器和数据流—数据目的
在Data Flow中Destination从数据源或者数据处理流程中接收数据。在SSIS中数据可以导入到任何OLE DB支持的数据源,平面文件或者Analysis Service中的数据。和数据源一样Destinations也通过连接管理器来连接,不同之处是有一个数据映射界面如图4-11。
1141 0
|
11天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1242 5
|
10天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1220 87