Golang 同步方式

2023-11-05

目录

1.channel 

2.Sync.Mutex

3. Sync.waitGroup

4. Sync.Once

5. Sync.context

6. Sync.pool

7.atomic包,针对变量进行操作

Sync包简述


收集了一些Golang中同步的方式,做一下笔记,未完待续。。

 

1.channel 

概述

Golang以如此明显的方式告诉我们:

优点:channel的核心是数据流动,关注到并发问题中的数据流动,把流动的数据放到channel中,就能使用channel解决这个并发

           问题,而且使用channel是线程安全的并且不会有数据冲突,比锁好用多了

缺点:不太适应同步太复杂的场景,比如多协程的同步等待问题,而且存在死锁问题 ,channel死锁问题:死锁问题链接

分类

channel类型:无缓冲和缓冲类型
channel有两种形式的,一种是无缓冲的,一个线程向这个channel发送了消息后,会阻塞当前的这个线程,知道其他线程去接收这个channel的消息。无缓冲的形式如下:

intChan := make(chan int)

带缓冲的channel,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理,带缓冲的形式如下:

//3为缓冲数量
intChan := make(chan int, 3)


举例


type Person struct {
	Name    string
	Age     uint8
	Address Addr
}

type Addr struct {
	city     string
	district string
}

/*
测试channel传输复杂的Struct数据
 */
func testTranslateStruct() {
	personChan := make(chan Person, 1)

	person := Person{"xiaoming", 10, Addr{"shenzhen", "longgang"}}
	personChan <- person

	person.Address = Addr{"guangzhou", "huadu"}
	fmt.Printf("src person : %+v \n", person)

	newPerson := <-personChan
	fmt.Printf("new person : %+v \n", newPerson)
}

在实际应用过程中,等待channel 结束信号的过程可能不是无期限的,一般会伴随一个timer,超时时间如下面所示:

/*
检查channel读写超时,并做超时的处理
 */
func testTimeout() {
	g := make(chan int)
	quit := make(chan bool)

	go func() {
		for {
			select {
			case v := <-g:
				fmt.Println(v)
			case <-time.After(time.Second * time.Duration(3)):
				quit <- true
				fmt.Println("超时,通知主线程退出")
				return
			}
		}
	}()

	for i := 0; i < 3; i++ {
		g <- i
	}

	<-quit
	fmt.Println("收到退出通知,主线程退出")
}


2.Sync.Mutex

 

3. Sync.waitGroup

Channel在某些同步场景下,使用略显复杂,不管是使用多个channel还是使用channel数组,如下:

func coordinateWithChan() {
 sign := make(chan struct{}, 2)
 num := int32(0)
 fmt.Printf("The number: %d [with chan struct{}]\n", num)
 max := int32(10)
 go addNum(&num, 1, max, func() {
  sign <- struct{}{}
 })
 go addNum(&num, 2, max, func() {
  sign <- struct{}{}
 })
 <-sign
 <-sign
}

所以Sync.waitGroup 就显得更为优雅,Sync.waitGroup 用来等待一组goroutines的结束,在主Goroutine里声明,并且设置要等待的goroutine的个数,每个goroutine执行完成之后调用 Done,最后在主Goroutines 里Wait即可。类似于JAVA中的CountDownLatch或者循环屏障,并且Sync.waitGroup可以被重复使用,提供了如下API:

func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()

但是Sync.waitGroup的使用需要遵循一些规则,避免抛出Panic:
a. 错误调用Done方法, 导致waitGroup内部计数值出现负数的情况

b. 错误的调用Add方法,在waitGroup内部计数值到达0的时候,Add方法被调用,导致应该被唤起的goroutine没有被唤起,就开始了新的一轮计数周期

所以在调用的时候,就要遵循一下原则:

      先统一Add,再并发Done,最后Wait

 

4. Sync.Once

      Sync.once实现方式是内部包含一个int32位的标志,用来判断方式是否被执行过,标志值更改的时机为方法执行完之后,当有多个goroutine进行调用的时候,使用double-check方式进行验证,首先在在没有同步方式的情况下,进行标志值的判定,为0则竞争获取mutex锁,进入临界区内,此时会在此进行标志值的判断,确保方法真的被执行一次。double-check第一次是为了更快的进行判断,但是存在错误的情况,第二次check是为了正确的确定标志值此时的状态。

使用:

func main() {
    var once sync.Once
    onceBody := func() {
        time.Sleep(3e9)
        fmt.Println("Only once")
    }
    done := make(chan bool)
    for i := 0; i < 10; i++ {
        j := i
        go func(int) {
            once.Do(onceBody)
            fmt.Println(j)
            done <- true
        }(j)
    }
    //给一部分时间保证能够输出完整【方法一】
    //for i := 0; i < 10; i++ {
    //    <-done
    //}

    //给一部分时间保证能够输出完整【方法二】
    <-done
    time.Sleep(3e9)
}

5. Sync.context

场景

当需要进行多批次的计算任务同步,或者需要一对多的协作流程的时候

使用举例

func coordinateWithContext() {
 total := 12
 var num int32
 fmt.Printf("The number: %d [with context.Context]\n", num)
 cxt, cancelFunc := context.WithCancel(context.Background())
 for i := 1; i <= total; i++ {
  go addNum(&num, i, func() {
   if atomic.LoadInt32(&num) == int32(total) {
    cancelFunc()
   }
  })
 }
 <-cxt.Done()
 fmt.Println("End.")
}

注意事项

a.如何生成自己的context

 通过WithCancel、WithDeadline、WithTimeout和WithValue四个方法从context.Background中派生出自己的子context

 注意context.background这个上下文根节点仅仅是一个最基本的支点,它不提供任何额外的功能,也就是说,它既不可以被撤销(cancel),也不能携带任何数据,在使用是必须通过以上4种方法派生出自己的context

b.子context是会继承父context的值

c.撤销消息的传播

撤销消息会按照深度遍历的方式传播给子context(注意因为多routine调用的原因,最终的撤销顺序可能不会是深度遍历的顺序)

,在遍历的过程中,通过WithCancel、WithDeadline、WithTimeout派生的context会被撤销,但是通过WithValue方法派生的context不会被撤销

 

 

6. Sync.pool

7.atomic包,针对变量进行操作

转载作者:吃猫的鱼0

转载地址:https://www.jianshu.com/p/228c119a7d0e

    我们调用sync/atomic中的几个函数可以对几种简单的类型进行原子操作。这些类型包括int32,int64,uint32,uint64,uintptr,unsafe.Pointer,共6个。这些函数的原子操作共有5种:增或减,比较并交换、载入、存储和交换它们提供了不同的功能,切使用的场景也有区别。

增或减

   顾名思义,原子增或减即可实现对被操作值的增大或减少。因此该操作只能操作数值类型。

   被用于进行增或减的原子操作都是以“Add”为前缀,并后面跟针对具体类型的名称。

//方法源码
func AddUint32(addr *uint32, delta uint32) (new uint32)

栗子:(在原来的基础上加n)

atomic.AddUint32(&addr,n)

栗子:(在原来的基础上加n(n为负数))

atomic.AddUint32(*addr,uint32(int32(n)))
//或
atomic.AddUint32(&addr,^uint32(-n-1))

比较并交换

   比较并交换----Compare And Swap 简称CAS

   他是假设被操作的值未曾被改变(即与旧值相等),并一旦确定这个假设的真实性就立即进行值替换

   如果想安全的并发一些类型的值,我们总是应该优先使用CAS

//方法源码
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)

栗子:(如果addr和old相同,就用new代替addr)

ok:=atomic.CompareAndSwapInt32(&addr,old,new)

载入

   如果一个写操作未完成,有一个读操作就已经发生了,这样读操作使很糟糕的。

   为了原子的读取某个值sync/atomic代码包同样为我们提供了一系列的函数。这些函数都以"Load"为前缀,意为载入。

//方法源码
func LoadInt32(addr *int32) (val int32)

栗子

fun addValue(delta int32){
    for{
        v:=atomic.LoadInt32(&addr)
        if atomic.CompareAndSwapInt32(&v,addr,(delta+v)){
            break;
        }
    }
}

存储

   与读操作对应的是写入操作,sync/atomic也提供了与原子的值载入函数相对应的原子的值存储函数。这些函数的名称均以“Store”为前缀

   在原子的存储某个值的过程中,任何cpu都不会进行针对进行同一个值的读或写操作。如果我们把所有针对此值的写操作都改为原子操作,那么就不会出现针对此值的读操作读操作因被并发的进行而读到修改了一半的情况。

   原子操作总会成功,因为他不必关心被操作值的旧值是什么。

//方法源码
func StoreInt32(addr *int32, val int32)

栗子

atomic.StoreInt32(被操作值的指针,新值)
atomic.StoreInt32(&value,newaddr)

交换

   原子交换操作,这类函数的名称都以“Swap”为前缀。

   与CAS不同,交换操作直接赋予新值,不管旧值。

   会返回旧值

//方法源码
func SwapInt32(addr *int32, new int32) (old int32)

栗子

atomic.SwapInt32(被操作值的指针,新值)(返回旧值)
oldval:=atomic.StoreInt32(&value,newaddr)

 

Sync包简述

1. 什么是Sync包?

Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.

Values containing the types defined in this package should not be copied.

这句话大意是说:
Sync包同步提供基本的同步原语,如互斥锁。 除了Once和WaitGroup类型之外,大多数类型都是供低级库例程使用的。 通过Channel和沟通可以更好地完成更高级别的同步。并且此包中的值在使用过后不要拷贝。

从描述中可以看到的是,golang 并不推荐这个包中的大多数并发控制方法,但还是提供了相关方法,主要原因是golang中提倡以共享内存的方式来通信:

不要以共享内存的方式来通信,作为替代,我们应该以通信的手段来共享内存

共享内存的方式使得多线程中的通信变得简单,但是在并发的安全性控制上将变得异常繁琐。
正确性不是我们唯一想要的,我们想要的还有系统的可伸缩性,以及可理解性,我觉得这点非常重要,比如现在广泛使用的Raft算法。

2. 包中的Type

包中主要有: Locker, Cond, Map, Mutex, Once, Pool,
RWMutex, WaitGroup

type Locker interface {
        Lock()
        Unlock()
}
type Cond struct {
        // L is held while observing or changing the condition
        L Locker
}

3. 什么是锁,为什么需要锁?

锁是sync包中的核心,他主要有两个方法,加锁和解锁。
在单线程运行的时候程序是顺序执行的,程序对数据的访问也是:
读取 => 一顿操作(加减乘除之类的) => 写回原地址
但是一旦程序中进行了并发编程,也就是说,某一个函数可能同时被不同的线程执行的时候,以时间为维度会发生以下情况:

 

可以看到的是,A地址的数字被执行了两次自增,若A=5,我们在执行完成后预期的A值是7,但是在这种情况下我们得到的A却是6,bug了~
还有很多类似的并发错误,所以才有锁的引入。若是我们在线程2读取A的值的时候对A进行加锁,让线程2等待,线程1执行完成之后在执行线程2,这样就能够保证数据的正确性。但是正确性不是我们唯一想要的。

4 写更优雅的代码

在很多语言中我们经常为了保证数据安全正确,会在并发的时候对数据加锁

Lock()
doSomething()
Unlock()

Golang在此包中也提供了相关的锁,但是标明了"most are intended for use by low-level library routines" 所以我这里只对 Once and WaitGroup types做简述。

5.Once 对象

Once 是一个可以被多次调用但是只执行一次,若每次调用Do时传入参数f不同,但是只有第一个才会被执行。

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

    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
    }

如果你执行这段代码会发现,虽然调用了10次,但是只执行了1次。BTW:这个东西可以用来写单例。

6. WaitGroup

。下面是个官方的例子:

var wg sync.WaitGroup
var urls = []string{
        "http://www.golang.org/",
        "http://www.google.com/",
        "http://www.somestupidname.com/",
}
for _, url := range urls {
        // Increment the WaitGroup counter.
        wg.Add(1)
        // Launch a goroutine to fetch the URL.
        go func(url string) {
                // Decrement the counter when the goroutine completes.
                defer wg.Done()
                // Fetch the URL.
                http.Get(url)
        }(url)
}
// Wait for all HTTP fetches to complete.
wg.Wait()

7. 简述

Golang中高级的并发可以通过channel来实现,这是golang所倡导的,但是go也提供了锁等先关操作。



 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang 同步方式 的相关文章

  • 【Golang入门】Golang第一天心得

    生活所迫 入门一下Go 很奇葩的第一点 接口 package main import fmt 定义一个接口 type Shape interface Area float64 定义一个矩形类型 type Rectangle struct W
  • go踩坑——no required module provides package go.mod file not found in current directory or any parent

    背景 准备运行下面代码 package main import github com gin gonic gin func main 创建一个默认的路由引擎 r gin Default GET 请求方式 hello 请求的路径 当客户端以G
  • Go语言入门【09】结构体

    结构体 相比于Java 在Go语言中没有类的概念 但是多了结构体 结构体与Java中的类很像 是表示一系列同一类型或不同类型的数据构成的数据集合 例如可以将学生抽象成一个结构体 每一个学生有以下属性 Name 姓名 Age 年龄 Gende
  • Go_关键字、编译、转义字符

    关键字 关键字是指被go语言赋予了特殊含义的单词 共25个 关键字不能用于自定义名字 只能在特定语法结构中使用 break default func interface select case defer go map struct cha
  • Qt webengine 显示web页面、前后端通信以及下载详解

    概述 官方文档 https doc qt io archives qt 5 11 qtwebengine overview html 翻译文档 Qt5 9 WebEngine 概述 一花一世界 一叶一乾坤 博客园 从Qt5 5开始 Qt W
  • golang:环境变量GOPROXY和GO111MODULE设置

    我们安装完golang后 我们在windows的cmd命令下就可以直接查看和使用go命令和环境变量了 同样的在linux下可以在控制台使用 如下图所示 C Users lijie1 gt go env set GO111MODULE set
  • go 进阶 go-zero相关: 七. 拦截器与熔断拦截器

    目录 一 拦截器的基础使用 1 服务端拦截器 2 客户端拦截器 二 拦截器底层底层执行原理 三 go zero默认添加的拦截器 客户端 1 熔断器拦截器 BreakerInterceptor 服务端 一 拦截器的基础使用 在go zero
  • 【golang】error parsing regexp: invalid or unsupported Perl syntax (正则表达式校验密码)

    要在 Go 中编写密码校验规则 确保密码不少于8位且包含数字和字母 你可以使用正则表达式和 Go 的 regexp 包来实现 以下是一个示例代码 错误示范 package main import fmt regexp func valida
  • Go中 Redis Client的使用

    文章目录 常见操作 List 操作 Pipeline 使用 在 Go 语言中使用 Redis 时 可以使用第三方库实现 Redis Client 的封装 本文介绍如何使用 Go 语言的 redisClient 去连接 Redis 服务器 并
  • goland环境配置

    goland modules环境配置 下载和安装goland 环境配置 配置环境变量GOPATH 配置go modules GOPROXY代理的系统变量 工程目录中新建三个工作目录 goland中启用go modules 新建一个go程序
  • Go 语言输出文本函数详解

    Go语言拥有三个用于输出文本的函数 Print Println Printf Print 函数以其默认格式打印其参数 示例 打印 i 和 j 的值 package main import fmt func main var i j stri
  • go-zero开发入门之gateway深入研究1

    创建一个 gateway 示例 main go package main import flag fmt gateway middleware github com zeromicro go zero core conf github co
  • “go mod tidy”之错误“not a valid zip file”

    执行 go mod tidy 时 遇到如下错误 rpc imports github com zeromicro go zero zrpc imports github com zeromicro go zero zrpc resolver
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • 协程-单线程内的异步执行

    1 仿协程实例 不同事件依次顺序执行 coding utf 8 import time def calculate 1 step event name for index in range step print This is s even
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • 这套Go语言开发框架组合真的非常高效

    我尝试过很多框架 从Django Flask和Laravel到NextJS和SvelteKit 到目前为止 这是我唯一可以使用的不会让我感到疯狂或者放弃项目的堆栈 框架 我喜欢所有这些框架 但我只是不太适应它们的设计方式 实际上 我是一个弱
  • golang 生成一年的周数

    GetWeekTimeCycleForGBT74082005 获取星期周期 中华人民共和国国家标准 GB T 7408 2005 参数 year 年份 GB T 7408 2005 func GetWeekTimeCycleForGBT74
  • Go、Docker、云原生学习笔记全攻略:从零开始,一步步走向精通!(2024版)

    第一章 Go语言学习宝典 一 介绍 01 Go 语言的前生今世 二 开发环境搭建 01 Go 语言开发环境搭建 三 初识GO语言 01 Go 多版本管理工具 02 第一个 Go 程序 hello world 与 main 函数 03 Go
  • 【go语言】读取toml文件

    一 简介 TOML 全称为Tom s Obvious Minimal Language 是一种易读的配置文件格式 旨在成为一个极简的数据序列化语言 TOML的设计原则之一是保持简洁性 易读性 同时提供足够的灵活性以满足各种应用场景 TOML

随机推荐

  • Ubuntu上vscode调试C/C++代码

    这篇文章起初是我看了一个B站的视频 作者讲述了如何在Ubuntu的 环境中通过使用vscode调试C C 代码 这个教程非常好 也非常推荐给大家 但是这个教程有一个局限性 就是他在他的公共号上写的教程非常简略 以至于我想再次看一遍 需要重新
  • 微信小程序引入背景图的三种方法

    1 直接在标签里加上style样式 加上背景图
  • K8s集群组件、flannel网络插件、Pod详解

    文章目录 Kubernetes 1 K8S集群架构 2 角色与功能 3 部署环境要求 Master Node 4 flannel插件 flannel是什么 目的 5 Pod 什么是Pod 为什么要使用Pod Pod的生命周期 Pod的创建过
  • STL源码:lis容器(Qt5.8版本)

    初次学习STL源码 很多语义尚且比较模糊 需待二次学习 源文件结构 主要的实现都在
  • Odoo 16 企业版手册 - CRM (2)

    销售线索 在与客户或组织开展业务之前 可以将销售线索视为第一步 如果个人或组织对您的业务感兴趣 您可以将他们的兴趣转换为您的业务 作为销售线索 稍后可以转换为销售机会 在Odoo CRM的帮助下 可以从各种来源收集线索 通过电话 短信 电子
  • windows下安装git

    一 下载Git安装包 1 打开Git的官方网站 https git scm com 2 找到下载页 https git scm com downloads 3 找到Windows版本下载页面 https git scm com downlo
  • 数据结构复杂度分析

    文章目录 前言 一 什么是复杂度分析 二 为什么要进行复杂度分析 三 如何进行复杂度分析 1 大O表示法 2 复杂度分析法则 四 常用的复杂度级别 1 常数阶O 1 2 线性阶O n 3 平方阶O n 4 对数阶O logn 五 不常见的时
  • golang中多种方式设置时区

    关于我 文章首发 我的博客 欢迎关注 go语言的time Now 返回的是当地时区时间 time Now Format 2006 01 02 15 04 05 time设置自定义时区 var cstSh time LoadLocation
  • c++继承-----继承中构造函数写法

    父类中的属性 调用父类的构造函数初始化 成员函数的方式初始化 子类中的构造函数 必须要调用父类构造函数 必须采用初始化参数列表的方式 子类想构造无参对象 父类必须要写无参构造函数 隐式调用构造函数 class Parent public 我
  • 文字验证码:简单有效的账号安全守卫!

    前言 文字验证码不仅是一种简单易懂的验证方式 同时也是保护您的账号安全的重要工具 通过输入正确的文字组合 您可以有效地确认自己的身份 确保只有真正的用户才能访问您的账号 HTML代码
  • 关于mybatis的resultMap映射VO类

    今天的模块需要用到多表联查 将查到的结果放到一个新的实体类中 而这几张表的主键我需要用到 难过的是多个表的主键名都是 id 这就导致新的实体类中多个表的主键字段名无法区分 最后再查询语句中加入别名以区分多个表的主键 本以为这就可以了 但是在
  • Java 通配符泛型例子

    请看下面的代码 其中会发生错误的代码已经注释掉 并且写明了错误类型 总体来说 泛型通配符就是为了支持多态时父子类 接口扩展类之间的相互转换而生 package test import java util ArrayList import j
  • seaborn学习笔记(三):直方图、条形图、条带图

    html font family sans serif ms text size adjust 100 webkit text size adjust 100 body margin 0 article aside details figc
  • [carla]把carla世界坐标系 转换为 俯视地图像素坐标系

    在下面这篇参考博客中介绍了如何手动获取从carla世界坐标系到俯视地图像素坐标系的旋转平移矩阵 我也是采用了一样的思路和代码 这里把实现的过程以及最后所有地图的变换矩阵记录如下 参考博客 carla真实世界坐标系与全局俯视地图像素坐标系变换
  • MetaFormer论文翻译

    MetaFormer A Unified Meta Framework for Fine Grained Recognition 摘要 细粒度视觉分类 FGVC 是一项需要识别属于超类别的多个从属类别的对象的任务 最近最先进的方法通常设计复
  • 七年程序员职业规划:北京、上海、硅谷工作经历分享

    前言 很多年前 刚刚从大学毕业的时候 很多公司来校招 其中最烂俗的一个面试问题是 你希望你之后三到五年的发展是什么 我当时的标准回答是 原话 成为在某一方面能够独当一面的技术专家 后来经历了几家不同的公司 换了不同的方向 才知道这个真是一个
  • SpringBoot为什么没有web.xml了

    SpringBoot为什么没有web xml了 今天我们来放松下心情 不聊分布式 云原生 来聊一聊初学者接触的最多的 java web 基础 几乎所有人都是从 servlet jsp filter 开始编写自己的第一个 hello worl
  • IDEA中快速查看maven依赖树关系, 以及快速解决jar包冲突

    安装Maven Helper 插件 打开pom xml 切换到Dependency Analyzer 即可看见jar包的传递依赖关系 比如 spring boot starter websocket 中已经包含了spring boot st
  • HW5300V3-ISCSI存储运维,看这一篇就够了04-创建启动器

    操作步骤 1 选择 资源分配 gt 主机 gt 启动器 单击 创建 2 系统弹出 创建启动器 对话框 在 类型 中选择启动器类型 为主机添加启动器 操作步骤 1 选择 资源分配 gt 主机 gt 启动器 根据业务需求 选择一个或多个待添加给
  • Golang 同步方式

    目录 1 channel 2 Sync Mutex 3 Sync waitGroup 4 Sync Once 5 Sync context 6 Sync pool 7 atomic包 针对变量进行操作 Sync包简述 收集了一些Golang