0
点赞
收藏
分享

微信扫一扫

Go语言的并发编程

海滨公园 2022-02-03 阅读 34

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

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

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

通道(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的声明形式为:

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

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

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

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

运行结果如下:

纽约 华盛顿 伦敦 北京 东京 

举报

相关推荐

0 条评论