Day9 Goroutine

并发 vs并行

并发运算就是多线程运算,且并发(concurrency)并非并行(Parallelism)

虽然两者从中文字面十分相似,但意义完全不同。

  • 并发共享时间运算,在一段时间内轮流享有时间资源。
  • 并行平行运算,在一段时间都能享有时间资源。
  • 并发是把时间切成很多小段,在这小段时间内先後执行多项任务。
  • 并行则是透过多核心同时处理多个任务。

以譬喻来说做两件事

  • 并发: 一个人在一段时间做两件事。
  • 并行:两个人同事在做一件事。

Goroutines

Goroutines 是轻量级的线程

main func 则是程序当前最主要的goroutine

Go Func

go function()

Go的并发会用到多个核心下去执行,试着执行以下的程序看看:

package main

import (
	"fmt"
	"time"
)

func main() {
	print1()
	print2()
	time.Sleep(time.Second)
}

func print1() {
	for i := 0; i < 100; i++ {
		fmt.Print("O")
	}
}

func print2() {
	for i := 0; i < 100; i++ {
		fmt.Print("X")
	}
}

运行後可得以下结果

OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

再加上关键字go之後

package main

import (
	"fmt"
	"time"
)

func main() {
	go print1()
	go print2()
	time.Sleep(time.Second)
}

func print1() {
	for i := 0; i < 100; i++ {
		fmt.Print("O")
	}
}

func print2() {
	for i := 0; i < 100; i++ {
		fmt.Print("X")
	}
}

运行後可得以下结果

OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXOOOOX

输出是一段一段的,一下一下X

交错着,代表两边的线程都很努力的想喷射把值Print出来。

由上面范例我们可以得知

  • 范例1是在print1()执行完後才执行print2(),并无并发情况发生。
  • 范例2则是都加上go关键字後执行,从结果上看来是有并发的。

runtime.GOMAXPROCS

runtime.GOMAXPROCS(n)这一参数限制程序执行时 CPU用到的最大核心数量。

如果设置小於1,等於没设,预设值是电脑核心数。

package main

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

func main() {
  runtime.GOMAXPROCS(2)
	go print1()
	go print2()
	time.Sleep(time.Second)
}

func print1() {
	for i := 0; i < 100; i++ {
		fmt.Print("O")
	}
}

func print2() {
	for i := 0; i < 100; i++ {
		fmt.Print("X")
	}
}

表示设定为2核心

Panic

Panic 是发生了预期之外的事情,导致异常、错误的产生,退出程序的同时回传错误代码2 (Process finished with exit code 2)。我们可以透过panic的func来主动引起错误发生。

要注意的是若在并发线程中发生了panic,也会导致主程序也异常结束。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Start")

	go p()
	time.Sleep(time.Second * 1)
	fmt.Println("End")
}

func p() {
	fmt.Println("Going to crash")
	panic("Crash!")
}

执行後得以下结果

Start
Going to crash
panic: Crash!

goroutine 34 [running]:
main.p()
	/tmp/sandbox3289633550/prog.go:18 +0x65
created by main.main
	/tmp/sandbox3289633550/prog.go:11 +0x65

虽然goroutine相当的便利,但不慎使用也会引发许多问题,最常见的就是Race Condition

竞争危害(Race Condition)

以下范例中,使用了10000个被并发出去的func,每个func只做一件事:count++。

package main

import (
	"fmt"
	"time"
)

var count = 0

func main() {
	for i := 0; i < 10000; i++ {
		go race()
	}
	time.Sleep(time.Millisecond * 100)
	fmt.Println(count)
}

func race() {
	count++
}

运行後得以下结果

9508

此时我们可以发现结果并非为10000,因为多个线程同时在争夺资源,导致有许多的数字都被重复执行了。

这种情况该如何对付呢?

Ans: 互斥锁,再多执行绪编成中,在对公共资源进行读写时,必须上锁防止其他线程争抢资源,并在结束读写时在解锁,让其他线程知道该资源已被释放。

sync.Mutex

If the lock is already in use, the calling goroutine blocks until the mutex is available.

为了保证total.value += i的原子性,我们通过sync.Mutex的锁来保证该语句在同一时间只被单一线程goroutine所访问。

package main

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

var count = 0
var m sync.Mutex

func main() {
	for i := 0; i < 10000; i++ {
		go race()
	}
	time.Sleep(time.Millisecond * 100)
	fmt.Println(count)
}

func race() {
	m.Lock()
	count++
	m.Unlock()
}

运行後可得以下结果

10000

只要在变数前上锁(Lock),在解锁(Unlock)前 只有该线程能对其进行操作。

Summary

这章节教大家最基本的goroutine使用,让大家在一些需要大量并发的情况下,能够使用最基本的goroutine来解决,那下一章节会针对goroutine在更深入的去介绍,下个章节我们也会围绕着sync这个标准库去解说。


<<:  Day 10 - [Zenbo开发系列] 07-DDE与App Builder

>>:  Date & time

Day22:今天来聊一下如何建立及管理 Azure Sentinel 工作区

部署Azure Sentinel环境牵涉到设计一个WorkSpace设定,以符合安全性和合规性需求。...

Day 6 Swift语法-基础篇(4/5)-Function

今天谈到最常用的函式 function 一般来说,函式的定义方式如图中所示 name: 代表函式的名...

Day2. Hello Matter.js World!

今天我们要来看到第一个画面了! 30天内所有写的扣笔者都会放在这个Git专案(https://git...

Day 07 line bot sdk python范例程序在做什麽

知道了line bot sdk python上的程序的功能是回复你和你传送相同的讯息。这边会看成是在...

[Day16] Functions

Cloud Function 是一款 Serverless 的服务,使用者不需要管理服务器。Goog...