goroutine基本用法

一. 基本用法

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() 阻塞主逻辑
package main

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

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

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

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

输出

hello
hello
hello
world
world
world
over!

三. 锁

1. 原子锁

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

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值。

func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})

注意点 01.

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

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") // 将导致一个恐慌
}

输出结果

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

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}}
}

输出结果

{{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

// 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
      // 进入临界区
   }
}

输出结果:

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 解锁之前再次进行加锁,会导致死锁
  • 适用于读写不确定,并且只有一个读或者写的场景
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()

}

输出结果

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 互斥锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爽朗地狮子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值