Go语言的并发编程

并发指在同一时间内可以执行多个任务。并发编程含义比较广泛,包含多线程编程、多进程编程及分布式程序等。

Go语言里的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为协程(goroutine)时,Go语言会将其视为一个独立的工作单元,这个单元会被调度到可用的逻辑处理器上执行。

并发编程基础

1、并发与并行(理论没看懂)

理解操作系统的线程(thread)和进程(process),有助于理解Go语言运行时调度器如何得用操作系统来并发运行goroutine。

并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

使用较少的资源做更多的事情,是指导Go语言设计的哲学。

2、指定使用核心数

Go语言默认会调用CPU核心数,这些功能都是自动调整的,但是也提供了相应的标准库来指定核心数。使用flags包可以调整程序运行时调用的CPU核心数。

代码如下:

package main

import (
	"fmt"
	"time"
)

func longWait() {
	fmt.Println("开始longWait()")
	time.Sleep(5 * 1e9)
	fmt.Println("结束longWait()")
}

func shortWait() {
	fmt.Println("开始shortWait()")
	time.Sleep(2 * 1e9)
	fmt.Println("结束shortWait()")
}

func main() {
	fmt.Println("这里是main()开始的地方")
	go longWait()
	go shortWait()
	fmt.Println("挂起main()")
	time.Sleep(10 * 1e9)
	fmt.Println("这里是main()结束的地方")
}

main()、longWait()和shortWait()三个函数作为独立的处理单元按顺序启动,然后开始并行运行,每一个函数都在运行的开始和结束阶段输出了消息。

运行结果如下:

这里是main()开始的地方
挂起main()
开始longWait()
开始shortWait()
结束shortWait()
结束longWait()
这里是main()结束的地方

如果移除go语言关键字同,重新运行程序:

这里是main()开始的地方
开始longWait()
结束longWait()
开始shortWait()
结束shortWait()
挂起main()
这里是main()结束的地方

协程(goroutine)

操作系统自己掌管的进程(process)、进程内的线程(thread)以及进程内的协程(coroutine,也叫轻量级线程)。

协程的最大优势在于其“轻量级”,可以轻松创建上百万个协程而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万个。

Go语言在语言级别支持轻量级线程,叫goroutine。

1、协程基础

goroutine是Go语言并行设计的核心。

goroutine是通过Go程序的runtime管理的一个线程管理器。goroutine通过go关键字实现了,其实就是一个普通的函数。

代码如下:

package main

import (
	"fmt"
	"runtime"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		runtime.Gosched()
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

运行结果如下:与书本不一样。

hello
hello
hello
hello
hello

书本是:

hello
world
hello
world
hello
world
hello
world
hello

并发处理:Go例程,代码如下:

package main

import (
	"fmt"
)

func routine1(index int) {
	for i := 0; i < 10; i++ {
		fmt.Println(index, " : ", i)
	}
}
func main() {
	go routine1(1)
	go routine1(2)
	for i := 0; i < 10; i++ {
		fmt.Println("0: ", i)
	}
}

运行结果如下:(感觉不对,不明白什么意思)

0:  0
0:  1
0:  2
0:  3
0:  4
0:  5
0:  6
0:  7
0:  8
0:  9

2、协程间通信

通常有两种最常见的并发通信模型:共享数据和消息。

代码如下:

package main

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

var counter int = 0

func Count(lock *sync.Mutex) {
	lock.Lock()
	counter++
	fmt.Println(z)
	lock.Unlock()
}

func main() {
	lock := &sync.Mutex{}
	for i := 0; i < 10; i++ {
		go Count(lock)
	}
	for {
		lock.Lock()
		c := counter
		lock.Unlock()
		runtime.Gosched()
		if c >= 10 {
			break
		}
	}
}

运行结果如下:

# command-line-arguments
./main.go:14:14: undefined: z

不要通过共享内存来通信,而应该通过通信来共享内存。

package main

import "fmt"

func Add(x, y int) {
	z := x + y
	fmt.Println(z)
}

func main() {
	for i := 0; i < 10; i++ {
		go Add(i, i)
	}
}

无运行结果。

通道(Channel)

代码如下:

package main

import (
	"fmt"
)

func Count(ch chan int) {
	ch <- 1
	fmt.Println("Counting")
}

func main() {
	chs := make([]chan int, 10)
	for i := 0; i < 10; i++ {
		chs[i] = make(chan int)
		go Count(chs[i])
	}
	for _, ch := range chs {
		<-ch
	}
}

1、基本语法

一般channel的声明形式为:

var chanName chan ElementType

在channel的用法中,最常见的包括写入和读出。

将一个数据写入(发送)到channel的语法很直观:

ch <- value

从channel中读取数据的语法是:

value:=<-ch

2、select

Go语言直接在语言级别支持select关键字,用于处理异步I/O问题。

3、缓冲机制

4、超时和计时器

5、channel的传递

6、单向channel

7、关闭channel

使用go语言内置的close()函数如下:

close(ch)

并发进阶

1、多核并行化

2、协程同步

只有在当需要告诉接受者不会再提供新的值的时候,才需要关闭通道。

只有发送者需要关闭通道,接收都永远不会需要。

代码如下:

package main

import (
	"fmt"
	"time"
)

func sendData(ch chan string) {
	ch <- "纽约"
	ch <- "华盛顿"
	ch <- "伦敦"
	ch <- "北京"
	ch <- "东京"
}

func getData(ch chan string) {
	var input string
	for {
		input = <-ch
		fmt.Printf("%s ", input)
	}
}

func main() {
	ch := make(chan string)
	go sendData(ch)
	go getData(ch)
	time.Sleep(1e9)
}

运行结果如下:

纽约 华盛顿 伦敦 北京 东京 

代码如下:

package main

import (
	"fmt"
)

func sendData(ch chan string) {
	ch <- "纽约"
	ch <- "华盛顿"
	ch <- "伦敦"
	ch <- "北京"
	ch <- "东京"
	close(ch)
}

func getData(ch chan string) {
	for {
		input, open := <-ch
		if !open {
			break
		}
		fmt.Printf("%s ", input)
	}
}

func main() {
	ch := make(chan string)
	go sendData(ch)
	getData(ch)
}

运行结果如下:

纽约 华盛顿 伦敦 北京 东京 

为一个普通函数创建goroutine的写法如下:

go 函数名(参数列表)

函数名:要调用的函数名。

参数列表:调用函数需要传入的参数。

使用go关键字创建goroutine时,被调用函数的返回值会被忽略。

代码如下:

package main

import (
	"fmt"
	"time"
)

func running() {
	var times int
	//构建一个无限循环
	for {
		times++
		fmt.Println("tick", times)
		//延时1秒
		time.Sleep(time.Second)
	}
}

func main() {
	//并发执行程序
	go running()
	//接受命令行输入,不做任何事情
	var input string
	fmt.Scanln(&input)
}

使用匿名函数创建goroutine的格式

go func (参数列表) {

        函数体

} (调用参数列表)

参数列表:函数体内的参数变量列表

函数体:匿名函数的代码。

调用参数列表:启动goroutine时,需要向匿名函数传递的调用函数。

代码如下:

package main

import (
	"fmt"
	"time"
)

func main() {

	go func() {
		var times int
		for {
			times++
			fmt.Println("tick", times)
			time.Sleep(time.Second)
		}
	}()
	var input string
	fmt.Scanln(&input)
}

调整并发的运行恒通(GOMAXPROCS)

格式如下:

runtime.GOMAXPROCS(逻辑CPU数量)

这里的逻辑CPU数量可以有如下几种数值:

<1:不修改任何数值。

=1:单核心执行。

>1:多核并发执行。

一般情况下,可以使用funtime.NumCPU()查询CPU数量 ,并使用runtime.GOMAXPROCS()函数进行设置,例如:

runtime.GOMAXPROCS(runtime.NumCPU())

理解并发和并行

并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一个时间点,任务并不会同时运行。

并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一个时间点,任务一定是同时运行。

代码如下:

package main

import (
	"fmt"
)

func sum(values []int, resultChan chan int) {
	sum := 0
	for _, value := range values {
		sum += value
	}
	resultChan <- sum		//将计算结果发送到channel中
}

func main() {
	values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	resultChan := make(chan int, 2)
	go sum(values[:len(values)/2], resultChan)
	go sum(values[len(values)/2:], resultChan)
	sum1, sum2 := <-resultChan, <-resultChan		//接收结果
	fmt.Println("Result:", sum1, sum2, sum1+sum2)
}

运行结果如下:

Result: 40 15 55

去程:

代码如下:

package main

import (
	"fmt"
	"time"
)

func sheep(i int) {
	for ; ; i += 2 {
		fmt.Println(i, "只羊")
	}
}

func main() {
	go sheep(1)
	go sheep(2)
	time.Sleep(time.Millisecond)
}

运行结果如下:

2 只羊
4 只羊
6 只羊
8 只羊
10 只羊
12 只羊
14 只羊
16 只羊
18 只羊
20 只羊
22 只羊
24 只羊
26 只羊
28 只羊
30 只羊
32 只羊
34 只羊
36 只羊
38 只羊
40 只羊
42 只羊
44 只羊
46 只羊
48 只羊
50 只羊
52 只羊
54 只羊
56 只羊
58 只羊
60 只羊
62 只羊
64 只羊
66 只羊
68 只羊
70 只羊
72 只羊
74 只羊
76 只羊
78 只羊
80 只羊
82 只羊
84 只羊
86 只羊
88 只羊
90 只羊
92 只羊
94 只羊
96 只羊
98 只羊
100 只羊
102 只羊
104 只羊
106 只羊
108 只羊
110 只羊

回程:

代码如下:

package main

import (
	"fmt"
	"time"
)

var wormhole chan time.Time

func deepspace() {
	wormhole <- time.Now()
}

func main() {
	wormhole = make(chan time.Time)
	go deepspace()
	fmt.Println(<-wormhole)
}

运行结果如下:

2022-04-06 15:47:02.3888929 +0800 CST m=+0.002617601

遍历与关闭:

代码如下:

package main

import (
	"fmt"
	"time"
)

type hole chan time.Time

func deepspace(w hole, h int) {
	defer close(w)
	for ; h > 0; h-- {
		w <- time.Now()
		time.Sleep(time.Second)
	}
}

func consumer(w hole) {
	for msg := range w {
		fmt.Println("consumer", msg)
	}
}

func main() {
	w := make(hole)
	go deepspace(w, 8)
	go consumer(w)
	for {
		msg, ok := <-w
		if !ok {
			break
		}
		fmt.Println("main", msg)
	}
	fmt.Println("Done")
}

运行结果如下:

main 2022-04-09 14:52:07.041789 +0800 CST m=+0.002800701
consumer 2022-04-09 14:52:08.0456268 +0800 CST m=+1.006638501
main 2022-04-09 14:52:09.0578227 +0800 CST m=+2.018834401
consumer 2022-04-09 14:52:10.0705782 +0800 CST m=+3.031589901
main 2022-04-09 14:52:11.0742367 +0800 CST m=+4.035248401
consumer 2022-04-09 14:52:12.0782694 +0800 CST m=+5.039281101
main 2022-04-09 14:52:13.0930031 +0800 CST m=+6.054014801
consumer 2022-04-09 14:52:14.0936872 +0800 CST m=+7.054698901
Done

MapReduce:

映射化简:每个去程对应一个Map并发执行,结果写入缓冲程道,最后再用一个Reduce函数综合这个程道得到的每一个值。

代码如下:

package main

import (
	"fmt"
	"math/rand"
)

type hole chan int

func deepspace(w hole) {
	w <- rand.Int()
}

func main() {
	n := 8
	w := make(hole, n)
	//Map
	for i := 0; i < n; i++ {
		go deepspace(w)
	}
	//Reduce
	t := 0
	for i := 0; i < n; i++ {
		t += <-w
	}
	fmt.Println("Total:", t)
}

运行结果 如下:

Total: -174686420686987236

两个goroutine进行并行的累加计算:代码如下:

package main

import (
	"fmt"
)

func sum(values []int, resultChan chan int) {
	sum := 0
	for _, value := range values {
		sum += value
	}
	resultChan <- sum
}

func main() {
	values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	resultChan := make(chan int, 2)
	go sum(values[:len(values)/2], resultChan)
	go sum(values[len(values)/2:], resultChan)
	sum1, sum2 := <-resultChan, <-resultChan
	fmt.Println("Result:", sum1, sum2, sum1+sum2)
}

运行结果如下:

Result: 40 15 55

select语句:

代码如下:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

type hole chan int

func deepspace(w hole, h int) {
	defer close(w)
	d := time.Duration(rand.Intn(h)) * time.Second
	for ; h > 0; h-- {
		w <- rand.Int()
		time.Sleep(d)
	}
}

func main() {
	n := 8
	w := make(hole)
	t := 0
	maxTime := time.Second
	go deepspace(w, n)
Out:
	for i := 0; i < n; i++ {
		select {
		case n := <-w:
			t += n
		case <-time.After(maxTime):
			fmt.Println("Time out")
			break Out
		}
	}
	fmt.Println("Total:", t)
}

运行结果如下:

Time out
Total: 8674665223082153551

程道值:

代码如下:

package main

import (
	"fmt"
)

type Read struct {
	key   string
	reply chan<- string
}

type Write struct {
	key string
	val string
}

var hole = make(chan interface{})

func deepspace() {
	m := map[string]string{}
	for {
		switch r := (<-hole).(type) {
		case Read:
			r.reply <- m[r.key] + " from Mars."
		case Write:
			m[r.key] = r.val
		}
	}
}

func main() {
	go deepspace()
	hole <- Write{"Name", "Martin"}
	home := make(chan string)
	hole <- Read{"Name", home}
	fmt.Println(<-home)
}

运行结果如下:

Martin from Mars.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值