Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >GO系列(4)-goroutine基本用法

GO系列(4)-goroutine基本用法

原创
作者头像
爽朗地狮子
发布于 2022-10-20 03:36:49
发布于 2022-10-20 03:36:49
3010
举报
文章被收录于专栏:云原生系列云原生系列

一. 基本用法

runtime 调度器是个非常有用的东西,关于 runtime 包几个方法:

  • Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行
  • NumCPU:返回当前系统的 CPU 核数量
  • GOMAXPROCS:设置最大的可同时使用的 CPU 核数
  • Goexit:退出当前 goroutine (但是 defer 语句会照常执行)
  • NumGoroutine:返回正在执行和排队的任务总数
  • GOOS:目标操作系统

二. 等待goroutine完成任务

  1. 创建一个 WaitGroup 实例,比如名称为:wg
  2. 调用 wg.Add(n),其中 n 是等待的 goroutine 的数量
  3. 在每个 goroutine 运行的函数中执行 defer wg.Done()
  4. 调用 wg.Wait() 阻塞主逻辑
代码语言:txt
AI代码解释
复制
package main

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

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    say2("hello", &wg)
    say2("world", &wg)
    fmt.Println("over!")
}

func say2(s string, waitGroup *sync.WaitGroup) {
    defer waitGroup.Done()

    for i := 0; i < 3; i++ {
        fmt.Println(s)
    }
}

输出

代码语言:txt
AI代码解释
复制
hello
hello
hello
world
world
world
over!

三. 锁

1. 原子锁

对于一个整数类型 Tsync/atomic 标准库包提供了下列原子操作函数。 其中 T 可以是内置 int32int64uint32uint64uintptr 类型

代码语言:txt
AI代码解释
复制
func AddT(addr *T, delta T)(new T)
func LoadT(addr *T) (val T)
func StoreT(addr *T, val T)
func SwapT(addr *T, new T) (old T)
func CompareAndSwapT(addr *T, old, new T) (swapped bool)

sync/atomic 标准库包也提供了一个 Value 类型。以它为基的指针类型 *Value 拥有两个方法: LoadStoreValue 值用来原子读取和修改任何类型的Go值。

代码语言:txt
AI代码解释
复制
func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})

注意点 01.

一旦 v.Store 方法( (v).Store 的简写形式)被曾经调用一次,则传递给值 v 的后续方法调用的实参的具体类型必须和传递给它的第一次调用的实参的具体类型一致; 否则,将产生一个恐慌。 nil 接口类型实参也将导致 v.Store() 方法调用产生恐慌。

比如:

代码语言:txt
AI代码解释
复制
package main

import (
   "fmt"
   "sync/atomic"
)

func main() {
   type T struct{ a, b, c int }
   var ta = T{1, 2, 3}
   var v atomic.Value
   v.Store(ta)
   var tb = v.Load().(T)
   fmt.Println(tb)       // {1 2 3}
   fmt.Println(ta == tb) // true

   v.Store("hello") // 将导致一个恐慌
}

输出结果

代码语言:txt
AI代码解释
复制
goroutine 1 [running]:
sync/atomic.(*Value).Store(0x14000110210, {0x102f47900, 0x102f58038})
        /opt/homebrew/Cellar/go@1.17/1.17.10/libexec/src/sync/atomic/value.go:77 +0xf4
main.main()
        /Users/tomxiang/github/go-demo/hello/routine/routine07.go:17 +0x218

Process finished with the exit code 2

注意点02

一个 CompareAndSwapT 函数调用传递的旧值和目标值的当前值匹配的情况下才会将目标值改为新值,并返回 true ;否则立即返回 false

代码语言:txt
AI代码解释
复制
import (
   "fmt"
   "sync/atomic"
)

func main() {
   type T struct{ a, b, c int }
   var x = T{1, 2, 3}
   var y = T{4, 5, 6}
   var z = T{7, 8, 9}
   var v atomic.Value
   v.Store(x)
   fmt.Println(v) // {{1 2 3}}
   old := v.Swap(y)
   fmt.Println("old:", old)
   fmt.Println("v:", v)            // {{4 5 6}}
   fmt.Println("old.(T)", old.(T)) // {1 2 3}
   swapped := v.CompareAndSwap(x, z)
   fmt.Println(swapped, v) // false {{4 5 6}}
   swapped = v.CompareAndSwap(y, z)
   fmt.Println(swapped, v) // true {{7 8 9}}
}

输出结果

代码语言:txt
AI代码解释
复制
{{1 2 3}}
old: {1 2 3}
v: {{4 5 6}}
old.(T) {1 2 3}
false {{4 5 6}}
true {{7 8 9}}

2. 互斥锁

例1. Gosched切换任务

mutex.Lock

代码语言:txt
AI代码解释
复制
// Package main 这个示例程序展示如何使用互斥锁来
// 定义一段需要同步访问的代码临界区
// 资源的同步访问
package main

import (
   "fmt"
   "runtime"
   "sync"
)

var (
   // counter是所有goroutine都要增加其值的变量
   counter int

   // wg用来等待程序结束
   wg sync.WaitGroup

   // mutex 用来定义一段代码临界区
   mutex sync.Mutex
)

// main 是所有Go程序的入口
func main() {
   // 计数加2,表示要等待两个goroutine
   wg.Add(2)

   // 创建两个goroutine
   go incCounter(1)
   go incCounter(2)

   // 等待goroutine结束
   wg.Wait()
   fmt.Printf("Final Counter: %d\n", counter)
}

// incCounter 使用互斥锁来同步并保证安全访问,
// 增加包里counter变量的值
func incCounter(id int) {
   // 在函数退出时调用Done来通知main函数工作已经完成
   defer wg.Done()

   for count := 0; count < 2; count++ {
      // 同一时刻只允许一个goroutine进入
      // 这个临界区
      mutex.Lock()
      { // 使用大括号只是为了让临界区看起来更清晰,并不是必需的。
         // 捕获counter的值
         value := counter

         // 当前goroutine从线程退出,并放回到队列
         runtime.Gosched()

         // 增加本地value变量的值
         value++

         // 将该值保存回counter
         counter = value
      }
      mutex.Unlock()
      // 释放锁,允许其他正在等待的goroutine
      // 进入临界区
   }
}

输出结果:

代码语言:txt
AI代码解释
复制
4

对 counter 变量的操作在第 46 行和第 60 行的 Lock() 和 Unlock() 函数调用定义的临界区里被保护起来。

同一时刻只有一个 goroutine 可以进入临界区。之后,直到调用 Unlock() 函数之后,其他 goroutine 才能进入临界区。当第 52 行强制将当前 goroutine 退出当前线程后,调度器会再次分配这个 goroutine 继续运行。当程序结束时,我们得到正确的值 4,竞争状态不再存在。

例2

  • 在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex
  • 使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
  • 在 Lock() 之前使用 Unlock() 会导致 panic 异常
  • 已经锁定的 Mutex 并不与特定的 goroutine 相关联,这样可以利用一个 goroutine 对其加锁,再利用其他 goroutine 对其解锁
  • 在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
  • 适用于读写不确定,并且只有一个读或者写的场景
代码语言:txt
AI代码解释
复制
package main

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

func main() {
   runtime.GOMAXPROCS(1)
   var mutex sync.Mutex
   wait := sync.WaitGroup{}

   fmt.Println("Locked Start")
   mutex.Lock()

   for i := 1; i <= 5; i++ {
      wait.Add(1)

      go func(i int) {
         fmt.Println("Not lock:", i)

         mutex.Lock()
         fmt.Println("Lock:", i)

         time.Sleep(time.Second)

         fmt.Println("Unlock:", i)
         mutex.Unlock()

         defer wait.Done()
      }(i)
   }

   time.Sleep(time.Second)
   fmt.Println("Unlocked finish")
   mutex.Unlock()

   wait.Wait()

}

输出结果

代码语言:txt
AI代码解释
复制
Locked Start
Not lock: 5
Not lock: 1
Not lock: 2
Not lock: 3
Not lock: 4
Unlocked finish
Lock: 5
Unlock: 5
Lock: 1
Unlock: 1
Lock: 2
Unlock: 2
Lock: 3
Unlock: 3
Lock: 4
Unlock: 4

参考链接

  1. Golang 入门 : 等待 goroutine 完成任务
  2. Golang 中 runtime 的使用
  3. sync/atomic 标准库包中提供的原子操作
  4. # Go 学习笔记(23)— 并发(02)[竞争,锁资源,原子函数sync/atomic、互斥锁sync.Mutex]
  5. Go 标准库 —— sync.Mutex 互斥锁

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go 并发编程之 Mutex
友情提示:此篇文章大约需要阅读 18分钟0秒,不足之处请多指教,感谢你的阅读。 订阅本站
Meng小羽
2020/11/23
6300
Go 并发编程之 Mutex
通俗易懂!图解Go协程原理及实战
导语 | 本文主要介绍一下线程、协程的原理,以及写成的基本使用,希望能对此方面感兴趣的开发者提供一些经验和启发。 引言 Golang的语法和运行时直接内置了对并发的支持。Golang里的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为goroutine时,Golang会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。 Golang运行时的调度器是一个复杂的软件,能管理被创建的所有goroutine并为其分配执行时间。这个调度器在操作系统之上,将操作系统的线程与语言运行时
腾讯云开发者
2022/07/27
1.3K0
通俗易懂!图解Go协程原理及实战
Go 语言互斥锁
在并发编程中,互斥锁(Mutex,全称 Mutual Exclusion)是一个重要的同步原语,用于确保多个线程或进程在访问共享资源时不会发生竞态条件。竞态条件是指在多个线程同时访问或修改共享数据时,由于操作顺序的不确定性,导致数据不一致或者程序行为不可预测的问题。
FunTester
2025/02/19
940
Go 语言互斥锁
Go 精妙的互斥锁设计
在并发编程中,互斥锁(Mutex)是控制并发访问共享资源的重要工具。Go 语言的互斥锁设计以其简洁、高效和易用性著称。本文将详细介绍 Go 语言中的互斥锁设计,探讨其内部实现原理,并展示如何在实际项目中正确使用互斥锁。
Michel_Rolle
2024/06/30
2.7K0
Golang并发编程控制
重学编程之Golang的plan中的上一篇文章我向大家介绍了,并发编程基础,goroutine的创建,channel,正由于go语言的简洁性,我们可以简易快速的创建任意个协程。同时也留下了许多隐患,如果没有更加深入的学习,其实很难直接将其运用到实际项目中,实际生活中。为什么呢?并发的场景许许多多,但一味的只知道其创建,是很难有效的解决问题。例如以下场景-资源竞争
PayneWu
2020/12/18
5740
Golang并发编程控制
Go并发编程之美-互斥锁
go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁、CAS、原子变量操作类。相比Java来说go提供了独特的基于通道的同步措施。本节我们先来看看go中互斥锁
加多
2019/02/15
3630
Java程序员学习Go指南(三)
转载:https://www.luozhiyun.com/archives/213
luozhiyun
2020/02/18
3030
Java程序员学习Go指南(三)
[Go] golang互斥锁mutex
1.互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区代码 2.Lock()和Unlock()定义临界区
唯一Chat
2019/09/10
9690
Golang 基础:底层并发原语 Mutex RWMutex Cond WaitGroup Once等使用和基本实现
上一篇 《原生并发 goroutine channel 和 select 常见使用场景》 介绍了基于 CSP 模型的并发方式。
张拭心 shixinzhang
2022/05/10
4100
Golang 基础:底层并发原语 Mutex RWMutex Cond WaitGroup Once等使用和基本实现
避坑:Go并发编程时,如何避免发生竞态条件和数据竞争
现在,我们已经知道了。在编写并发程序时,如果不谨慎,没有考虑清楚共享资源的访问方式和同步机制,那么就会发生竞态条件和数据竞争这些问题,那么如何避免踩坑?避免发生竞态条件和数据竞争的办法有哪些?请看下面:
不背锅运维
2023/04/25
1K0
避坑:Go并发编程时,如何避免发生竞态条件和数据竞争
Go语言入门(八)线程安全&锁
线程安全&锁 定时器&一次性定时器 定时器 func main() { ticker := time.NewTicker(time.Second) //ticker.C是一个只读的chan,所以直接可以使用for range读取 for v := range ticker.C { fmt.Printf("hello %v\n",v) //按秒输出 } } 一次性定时器 func main() { select { case <- time.After(ti
alexhuiwang
2020/09/24
3910
Go:深入理解互斥锁,实现与应用
在并发编程中,互斥锁是一种基本的同步机制,用于保护共享资源不被多个线程或进程同时访问,从而避免数据竞争和保证数据的一致性。本文将深入探讨互斥锁的概念、工作原理,并通过Go语言的具体实现来展示互斥锁在实际编程中的应用。
运维开发王义杰
2024/05/10
2570
Go:深入理解互斥锁,实现与应用
盘点Golang并发那些事儿之二
上一节提到,golang中直接使用关键字go创建goroutine,无法满足我们的需求。主要问题如下
PayneWu
2021/06/10
5100
盘点Golang并发那些事儿之二
GO的锁和原子操作分享
要是对协程的使用感兴趣的话,可以看看这篇文章简单了解一下瞅一眼就会使用GO的并发编程分享
阿兵云原生
2023/02/16
3280
go的并发小知识
从第3点钟的操作状态表中可以看到,我们有四种操作会导致goroutine阻塞,三种操作会导致程序panic!因此,为了尽可能转移这些风险,我们需要分配channel的所有权。即,channel的所有者做实例化、写入和关闭操作;channel的使用者做读取操作,且约束其他人无法对其做相应的操作。一个优雅的实现:
天地一小儒
2022/12/28
2290
go的并发小知识
Go 专栏|并发编程:goroutine,channel 和 sync
原文链接: Go 专栏|并发编程:goroutine,channel 和 sync
AlwaysBeta
2021/09/16
6700
Go 专栏|并发编程:goroutine,channel 和 sync
Go语言核心36讲(Go语言实战与应用九)--学习笔记
我们在前几次讲的互斥锁、条件变量和原子操作都是最基本重要的同步工具。在 Go 语言中,除了通道之外,它们也算是最为常用的并发安全工具了。
郑子铭
2021/11/21
2230
Go语言核心36讲(Go语言实战与应用九)--学习笔记
协程锁
我们对一个变量total 进行1000次 +1 操作,不过我们是在多个协程中进行的,猜猜结果如何,我们运行五次看结果
酷走天涯
2019/06/11
5780
协程锁
《GO IN ACTION》读后记录:GO的并发与并行
一、使用goroutine来运行程序 1. Go的并发与并行 Go的并发能力,是指让某个函数独立于其他函数运行的能力。当为一个函数创建goroutine时,该函数将作为一个独立的工作单元,被 调度器 调度到可用的逻辑处理器上执行。Go的运行时调度器是个复杂的软件,它做的工作大致是: 管理被创建的所有goroutine,为其分配执行时间 将操作系统线程与语言运行时的逻辑处理器绑定 参考The Go scheduler ,这里较浅显地说一下Go的运行时调度器。操作系统会在物理处理器上调度操作系统线程来运行,而G
李海彬
2018/03/28
9880
《GO IN ACTION》读后记录:GO的并发与并行
浅谈Go并发原语
在操作系统中,往往设计一些完成特定功能的、不可中断的过程,这些不可中断的过程称为原语。
闫同学
2024/02/12
4010
相关推荐
Go 并发编程之 Mutex
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档