Golang 并发处理

文章目录

    • 一、基本概念
    • 二、sync.WaitGroup
    • 三、goroutine 和线程
    • 四、channel
    • 五、无缓冲通道和缓冲通道
    • 六、生产者和消费者模型
    • 七、select 多路复用
    • 八、单向通道
    • 总结


一、基本概念

  • 并发:
    是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
    (同一时间段同时在做多个事情)

  • 并行:
    在操作系统中是指,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。
    (同一时刻同时在做多个事情)

  • 进程:
    —个程序启动之后就创建了—个进程

  • 线程:
    操作系统调度的最小单元

  • 协程:
    协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源

  • goroutine:
    go关键字为一个函数创建一个goroutine。一个函数可以被创建多个goroutine,一个 goroutine 必定对应一个函数

启动单个goroutine,只需在调用的函数(普通函数和匿名函数)前面加上一个go关键字

  • 示例:等待goroutine运行完main函数再结束
package main

import (
	"fmt"
	"time"
)

func hello() {
	fmt.Println("hello函数")
}

func main() {
	//并发执行
	go hello()
	//休眠1s
	time.Sleep(time.Second)
	fmt.Println("main函数")
}


//输出结果如下
hello函数
main函数
  • 加入 defer语句
package main

import (
	"fmt"
	"time"
)

func hello() {
	fmt.Println("hello函数")
}

func main() {
	defer fmt.Println("defer 语句")  //main函数结束之前执行
	//并发执行
	go hello() 						//先创建一个goroutine;再goroutine中执行hello函数
	fmt.Println("main函数")
	//休眠1s
	time.Sleep(time.Second)
}


//输出结果如下
main函数
hello函数
defer 语句
  • 上面的代码中主线程为了等待goroutine都运行完毕,不得不在程序的末尾使用time.Sleep()来睡眠一段时间,等待其他线程充分运行。对于简单的代码,100个for循环可以在1秒之内运行完毕,time.Sleep()也可以达到想要的效果。
  • 但是对于实际生活的大多数场景来说,1秒是不够的,并且大部分时候我们都无法预知for循环内代码运行时间的长短。这时候就不能使用 time.Sleep() 来完成等待操作了。

二、sync.WaitGroup

  • WaitGroup 对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait() 用来控制计数器的数量。Add(n) 把计数器设置为n ,Done() 每次把计数器-1 ,wait() 会阻塞代码的运行,直到计数器地值减为0
package main

import (
	"fmt"
	"sync"
)

//定义sync.WaitGroup结构体,内置计数器
var sw sync.WaitGroup

func hello() {
	fmt.Println("hello 函数")
	//执行完成通知,次数-1
	sw.Done()
}

func test() {
	fmt.Println("test 函数")
	sw.Done()
}

func main() {
	defer fmt.Println("defer 语句")
	//执行前告知main打标记
	//设置计数器次数
	sw.Add(2)
	go hello()
	go test()
	fmt.Println("main 函数")
	//阻塞:一直等到所有goroutine执行结束
	sw.Wait()
}



//输出结果如下
hello 函数
main 函数
test 函数
defer 语句
  • 启用10个goroutine
package main

import (
	"fmt"
	"sync"
)

//启用10个goroutine

var sw sync.WaitGroup

func hello(i int) {
	fmt.Println("hello 函数", i)
	sw.Done()
}

func main() {
	sw.Add(10)
	//创建10个goroutine
	for i := 0; i < 10; i++ {
		go hello(i)
	}
	fmt.Println("main 函数")
	sw.Wait()
}


//输出结果如下
hello 函数 9
hello 函数 5
hello 函数 2
hello 函数 8
hello 函数 1
hello 函数 7
hello 函数 3
hello 函数 4
hello 函数 6
main 函数
hello 函数 0
  • panic宕机前把错误信息发送到控制台上,程序结束,资源全部释放
package main

import (
	"fmt"
	"sync"
)

//启用10个goroutine

var sw sync.WaitGroup

func hello(i int) {
	fmt.Println("抢购商品流程", i)
	if i == 6 {
		panic("宕机报警")
	}
	sw.Done()
}

func main() {
	sw.Add(10)
	//创建10个goroutine
	for i := 0; i < 10; i++ {
		go hello(i)
	}
	sw.Wait()
}



//输出结果如下:
抢购商品流程 9
抢购商品流程 4
抢购商品流程 5
抢购商品流程 6
抢购商品流程 3
抢购商品流程 1
抢购商品流程 2
抢购商品流程 0
抢购商品流程 8
panic: 宕机报警

goroutine 12 [running]:
main.hello(0x6)
	d:/goproject/src/dev_code/test04/main/main.go:15 +0xb7
created by main.main
	d:/goproject/src/dev_code/test04/main/main.go:24 +0x57
exit status 2

可以看到panic影响到了整个main程序,触发后会释放掉内存

三、goroutine 和线程

局部变量放在栈中,全局变量在堆中

  • 可增长的栈
    OS线程(操作系统线程)一般都有固定的栈内存(2MB),一个goroutine的栈在生命周期开始时只有很小的栈(2KB),goroutine的栈是不固定的,可以按需增加或者缩小,goroutine的栈大小限制可以达到1GB,虽然这种情况不多见,所以一次可以创建十万左右的goroutine是没问题的。

  • goroutine调度
    OS线程由OS内核来调度,goroutine则是由Go运行时(runtime) 自己的调度器来调度,这个调度器使用一个m:n调度的技术(复用/调度m个goroutine到n个OS线程),goroutine的调度不需要切换内核语境,所以调用一个goroutine 比调用一个线程的成本要低很多。

  • GOMAXPROCS
    Go运行时的调度使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码,默认值是机器上的CPU核心数。例如:在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n) 。

  • Go可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
    (Go1.5版本前默认是单核心执行,Go1.5版本后默认使用全部逻辑核心数)

示例:
通过将任务分配到不同的CPU逻辑核心上实现并行的效果

package main

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

//计数器结构体
var sw sync.WaitGroup

func a() {
	defer sw.Done()
	for i := 0; i < 20; i++ {
		fmt.Println("A:", i)
	}
}

func b() {
	defer sw.Done()
	for i := 0; i < 20; i++ {
		fmt.Println("B:", i)
	}
}

func main() {
	//资源分配1个逻辑核心数
	runtime.GOMAXPROCS(1)
	sw.Add(2)
	go a()
	go b()
	sw.Wait()
}


//输出结果如下
B: 0
B: 1
B: 2
B: 3
B: 4
B: 5
B: 6
B: 7
B: 8
B: 9
B: 10
B: 11
B: 12
B: 13
B: 14
B: 15
B: 16
B: 17
B: 18
B: 19
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19

四、channel

  • 单纯的将函数并发执行没有意义,函数与函数间需要交换数据的时候才能体现并发执行函数的意义。
  • 虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题
  • go语言的并发模型是SCP,提倡通过通信共享内存而不是通过共享内存而实现通信
  • 如果说 goroutine 是Go程序并发的执行体,channel就是它们之间连接
  • channel 是可以让一个 goroutine 发送特定值到另一个goroutine的通信机制
  • go语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,在声明channel的时候需要为其指定元素类型。

语法:

var 变量 chan 元素类型

var ch1 chan int		//传递整形的通道
var ch2 chan bool		//传递布尔型的通道
var ch3 chan []int		//传递整形切片的通道
  • 示例:
    使用channel传输数据
package main

import "fmt"

//channel定义
func main() {
	//channel是引用类型
	var ch1 chan int  //通道ch1传输int元素数据
	var ch2 chan bool //通道ch2传输bool元素数据
	fmt.Println("ch1: ", ch1)
	fmt.Println("ch2: ", ch2)
}

//初始结果如下
ch1:  <nil>
ch2:  <nil>
  • 传递二个值
package main

import "fmt"

//channel定义
func main() {
	//使用make初始化channel
	ch3 := make(chan int, 2)
	//发送
	ch3 <- 10
	ch3 <- 20
	//接收
	result := <-ch3
	result2 := <-ch3
	fmt.Println("ch3: ", result, result2)
}


//输出结果如下
ch3:  10 20
  • 关闭通道后是否能继续取值?
package main

import "fmt"

//channel定义
func main() {
	//使用make初始化channel
	ch3 := make(chan int, 2)
	//发送
	ch3 <- 10
	ch3 <- 20
	//接收
	result := <-ch3
	result2 := <-ch3
	fmt.Println("ch3: ", result, result2)
	//关闭通道
	close(ch3)
	result = <-ch3
	fmt.Println("ch3关闭后接收的值: ", result)
}



//输出结果如下
ch3:  10 20
ch3关闭后接收的值:  0

Golang 并发处理_第1张图片

  • 关闭的通道能否再次关闭?
    会出现panic
    Golang 并发处理_第2张图片

  • 传递并取出多个值

package main

import "fmt"

func main() {
	var ch = make(chan int, 10)
	for i := 0; i < 10; i++ {
		ch <- i
		if i == 6 {
			close(ch)
			break
		}
	}
	//通道元素数量
	leng := len(ch)
	fmt.Printf("ch元素格式 %d, 容量 %d\n", leng, cap(ch))   //cap()取容量
	for i := 0; i < leng; i++ {
		result := <-ch
		fmt.Println(result)
	}
	
	//数据量不确定的情况下取数据使用for range
	/* for result := range ch {
		fmt.Println(result)
	} */
}



//输出结果如下
ch元素格式 7, 容量 10
0
1
2
3
4
5
6

五、无缓冲通道和缓冲通道

  • 无缓冲通道,同步处理
package main

import "fmt"

//通道取值
func Result(ch chan bool) {
	//取值,未取到会处于阻塞状态
	ret := <-ch
	fmt.Println(ret)
}

func main() {
	var ch = make(chan bool)
	go Result(ch)
	//传递数据,同步执行
	ch <- true
	fmt.Println("main 函数")
}

//输出结果如下
true
main 函数
  • 缓冲通道,异步处理
package main

import (
	"fmt"
	"time"
)

//通道取值
func Result(ch chan bool) {
	ret := <-ch
	fmt.Println(ret)
}

func main() {
	//缓冲通道
	var ch = make(chan bool, 10)
	ch <- false
	//获取数据量
	fmt.Println(len(ch), cap(ch))
	go Result(ch)
	ch <- true
	time.Sleep(time.Second)
	fmt.Println("main 函数结束")
}

//输出结果如下
1 10
false
main 函数结束
  • 取值时判断通道是否关闭
package main

import "fmt"

//产生数据输入通道,输入完立即关闭
func Send(ch chan int) {
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)
}

func main() {
	var ch = make(chan int, 100)
	go Send(ch)
	//从通道中取值
	for {
		result, ok := <-ch
		if !ok {			//值取完进行退出
			break
		}
		fmt.Println(result)
	}

	/*for result := range ch {
		fmt.Println(result)
	}*/
}

//输出结果如下
0
1
2
3
4
5
6
7
8
9

六、生产者和消费者模型

生产者——》产生随机数
消费者——》计算每个随机数的每个位的数字的和

package main

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

//随机数通道
var itemChan chan *item

//随机数结构体
type item struct {
	id  int64
	num int64
}

//求和通道
var resultChan chan *result

//求和结构体
type result struct {
	item *item
	sum  int64
}

//生产者
func producer(ch chan *item) {
	//生成随机数
	var id int64
	for {
		id++
		number := rand.Int63() //随机正整数
		//构造结构体
		tmp := &item{
			id:  id,
			num: number,
		}
		//随机数发送到通道中
		ch <- tmp
		time.Sleep(time.Millisecond * 100)
	}
}

//计算求和
func calc(i int64) int64 {
	//求和变量
	var sum int64
	for i > 0 {
		//得到每一个位数进行累加
		sum = sum + i%10
		i = i / 10
	}
	return sum
}

//消费者
func consumer(ch chan *item, resultChan chan *result) {
	for {
		tmp := <-ch
		//数据运算
		sum := calc(tmp.num) //tmp.num 就是(*tmp).name
		//构造result结构体
		resObj := &result{
			item: tmp,
			sum:  sum,
		}
		//结果传递给通道result等待进行输出
		resultChan <- resObj
	}
}

//打印结果
func printResult(resultChan chan *result) {
	for ret := range resultChan {
		fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
		time.Sleep(time.Second)
	}
}

//指定数量的goroutine
func startWorker(n int, ch chan *item, resultChan chan *result) {
	for i := 0; i < n; i++ {
		go consumer(ch, resultChan)
	}
}

func main() {
	//通道初始化,结构体指针类型
	itemChan = make(chan *item, 100)
	resultChan = make(chan *result, 100)
	//启用生产者goroutine
	go producer(itemChan)
	//消费者gotoutine
	startWorker(30, itemChan, resultChan)
	printResult(resultChan)
}



//输出结果如下
id:1,num:5577006791947779410,sum:95
id:2,num:8674665223082153551,sum:79
id:3,num:6129484611666145821,sum:81
id:4,num:4037200794235010051,sum:53
id:5,num:3916589616287113937,sum:95
id:6,num:6334824724549167320,sum:80
id:7,num:605394647632969758,sum:99
id:8,num:1443635317331776148,sum:77
id:9,num:894385949183117216,sum:89
id:10,num:2775422040480279449,sum:80
id:11,num:4751997750760398084,sum:99
...
...
...
  • 产生固定数量数据(10000),消费并输出
package main

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

//计数器处理消费者goroutine
var sw sync.WaitGroup

//随机数通道
var itemChan chan *item

//随机数结构体
type item struct {
	id  int64
	num int64
}

//求和通道
var resultChan chan *result

//求和结构体
type result struct {
	item *item
	sum  int64
}

//生产者
func producer(itemCh chan *item) {
	//生成随机数
	var id int64
	for i := 0; i < 10000; i++ {
		id++
		number := rand.Int63() //随机正整数
		//构造结构体
		tmp := &item{
			id:  id,
			num: number,
		}
		//随机数发送到通道中
		itemCh <- tmp
	}
	//生产完成关闭itemChan通道
	close(itemCh)
}

//计算求和
func calc(i int64) int64 {
	//求和变量
	var sum int64
	for i > 0 {
		//得到每一个位数进行累加
		sum = sum + i%10
		i = i / 10
	}
	return sum
}

//消费者
func consumer(itemch chan *item, resultChan chan *result) {
	defer sw.Done()
	for tmp := range itemch {
		//数据运算
		sum := calc(tmp.num) //tmp.num 就是(*tmp).name
		//构造result结构体
		resObj := &result{
			item: tmp,
			sum:  sum,
		}
		//结果传递给通道result等待进行输出
		resultChan <- resObj
	}
}

//打印结果
func printResult(resultChan chan *result) {
	for ret := range resultChan {
		fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
	}
}

//指定数量的goroutine
func startWorker(n int, ch chan *item, resultChan chan *result) {
	for i := 0; i < n; i++ {
		go consumer(ch, resultChan)
	}
}

func main() {
	//通道初始化,结构体指针类型
	itemChan = make(chan *item, 10000)
	resultChan = make(chan *result, 10000)
	//启用生产者goroutine
	go producer(itemChan)
	//启用计数器
	sw.Add(30)
	//消费者gotoutine
	startWorker(30, itemChan, resultChan)
	//等待goroutine结束
	sw.Wait()
	//关闭resultChan通道
	close(resultChan)
	printResult(resultChan)
}


//输出结果如下
id:1,num:5577006791947779410,sum:95
id:2,num:8674665223082153551,sum:79
id:3,num:6129484611666145821,sum:81
id:4,num:4037200794235010051,sum:53
...
id:9997,num:661484091736918950,sum:87
id:9998,num:5665788214883279410,sum:94
id:9999,num:3873652276866279948,sum:108
id:10000,num:8455728612988973956,sum:112
  • 方法②:通道和goroutine配合处理指定数量数据
package main

import (
	"fmt"
	"math/rand"
)

//随机数通道
var itemChan chan *item

//求和管道
var resultChan chan *result

//空结构体通道
var doneChan chan struct{}

//随机数结构体
type item struct {
	id  int64
	num int64
}

//求和结构体
type result struct {
	item *item
	sum  int64
}

//生产者
func producer(itemCh chan *item) {
	var id int64
	//指定数量数据
	for i := 0; i < 10000; i++ {
		//序列号自增
		id++
		number := rand.Int63() //随机正整数
		//随机数结构体封装
		tmp := &item{
			id:  id,
			num: number,
		}
		//随机数放入通道
		itemCh <- tmp
		// time.Sleep(time.Millisecond*100)
	}
	//itemCh通道关闭
	close(itemCh)
}

//求和运算
func calc(i int64) int64 {
	//求和变量
	var sum int64
	for i > 0 {
		//每位数字求和
		sum = sum + i%10
		i = i / 10
	}
	return sum
}

//消费者
func consumer(itemCh chan *item, resultCh chan *result) {
	for tmp := range itemCh {
		//数据运算
		sum := calc(tmp.num)
		//求和结构体封装
		result := &result{
			item: tmp,
			sum:  sum,
		}
		//求和数放入resultChan通道
		resultCh <- result
	}
	//传递空结构体进通道
	doneChan <- struct{}{}
}

//输出遍历
func printResult(resultChan chan *result) {
	for ret := range resultChan {
		fmt.Printf("id:%v;num:%v;sum:%v\n", ret.item.id, ret.item.num, ret.sum)
		//节奏输出控制
		// time.Sleep(time.Second)
	}
}

//消费者goroutine数量控制
func startWorker(n int, ch chan *item, resultCh chan *result) {
	//开启n数量的goroutine
	for i := 0; i < n; i++ {
		//goroutine调用函数
		go consumer(ch, resultCh)
	}
}

//监控goroutine即时关闭通道
func closeChan(n int, doneChan chan struct{}, resultChan chan *result) {
	//监控goroutine次数
	for i := 0; i < n; i++ {
		<-doneChan
	}
	//计数通道关闭
	close(doneChan)
	//求和通道关闭
	close(resultChan)
}

func main() {
	//通道初始化,结构体指针类型
	itemChan = make(chan *item, 10000)
	resultChan = make(chan *result, 10000)
	doneChan = make(chan struct{}, 30)
	//启用生产者goroutine
	go producer(itemChan)

	//消费者goroutine,高并发处理
	startWorker(30, itemChan, resultChan)
	//启用goroutine监控流程是否结束
	go closeChan(30, doneChan, resultChan)

	//调用输出函数
	printResult(resultChan)
}

七、select 多路复用

  • 类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。
  • select 随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。
  • 某些场景下,我们需要同事从多个通道接受数据,没有数据发生接收就会阻塞,我们往往可以使用一下方式解决。

语法示例:

select {
case communication clause:
	statement(s);
case communication clause:
	statement(s);
/* 你可以定义任何数量的case*/
default : /* 可选 */statement(s);
}
  • 每个case 都必须是一个通信

  • 所有channel表达式都会被求值

  • 所有被发送的表达式都会被求值

  • 如果任意某个通信可以进行,它就执行,其他被忽略。

  • 示例:

package main

import (
	"fmt"
	"math"
	"time"
)

//select语句
var ch1 = make(chan string, 100)
var ch2 = make(chan string, 100)

//发送数据k1
func sendK1(ch chan string) {
	//产生数据
	for i := 0; i < math.MaxInt64; i++ {
		//像ch中发送数据
		ch <- fmt.Sprintf("k1:%d\n", i)
		//50毫秒放入一个数据
		time.Sleep(time.Millisecond * 50)
	}
}

//发送数据k2
func sendK2(ch chan string) {
	//产生数据
	for i := 0; i < math.MaxInt64; i++ {
		//像ch中发送数据
		ch <- fmt.Sprintf("k2:%d\n", i)
		//100毫秒放入一个数据
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	go sendK1(ch1)
	go sendK2(ch2)
	//取值
	for {
		//如果通道都可通信,是随机公平执行其中一条,忽略其他;
		//如果通道都不可通信,且没有default语句时候,处于阻塞状态,直到可以通信为止
		select {
		case ret := <-ch1:
			fmt.Println(ret)
		case ret := <-ch2:
			fmt.Println(ret)
		default:
			fmt.Println("没有数据可取")
			time.Sleep(time.Millisecond * 500)
		}
	}
}


//输出结果如下

没有数据可取
k2:0

k2:1

k2:2

k1:0

k1:1

k2:3

k2:4

k1:2
...
package main

import "fmt"

//select case通信原理
func main() {
	var ch = make(chan int, 1)
	for i := 0; i < 10; i++ {
		//解决死锁问题
		select {
		case ch <- i:
		case ret := <-ch:
			fmt.Println(ret)
		}
	}
}


//输出结果如下
0
2
4
6
8
  • 使用select 完善生产者和消费者模型,键盘输入回车终止数据
package main

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

// 传送随机数通道(使用指针传递)
var itemChan chan *item

// 传送求和值通道
var resultChan chan *result

// 空结构体传输通道
var doneChan chan struct{}

//随机数字结构体
type item struct {
	id  int64
	num int64
}

//求和结构体
type result struct {
	item *item
	sum  int64
}

//生产者函数
func producer(ch chan *item) {
	// 1.生成随机数
	var id int64
	for {
		// 序列号自增
		id++
		number := rand.Int63() // 随机生成正整数
		// 随机数结构体封装
		tmp := &item{
			id:  id,
			num: number,
		}
		// 2. 随机数发送到通道中
		ch <- tmp
	}
}

//计算求和函数
func calc(num int64) int64 {
	// 和:值
	var sum int64
	for num > 0 {
		// 得到每一个位数进行累加
		sum = sum + num%10
		num = num / 10
	}
	return sum
}

//消费者函数
func consumer(ch chan *item, resultChan chan *result) {
	// 从 itemChan 通道中取随机数结构体指针
	for tmp := range ch {
		sum := calc(tmp.num) // tmp.num 就是 (*tmp).num,会自动识别指针
		// 构造 result 结构体
		resObj := &result{
			item: tmp,
			sum:  sum,
		}
		// 结果传递通道 resultChan 等待进行输出
		resultChan <- resObj
	}
}

// 打印结果
func printResult(doneChan chan struct{}, resultChan chan *result) {
	for {
		// 等待 doneChan 输入退出输出信息
		select {
		case ret := <-resultChan:
			fmt.Printf("id:%v,num:%v,sum:%v\n", ret.item.id, ret.item.num, ret.sum)
			time.Sleep(time.Second)
		case <-doneChan:
			return
		}
	}
}

// 监听键盘输入字符传递给 doneChan 通道
func inputChan(doneChan chan struct{}) {
	// 一个字符的输入
	tmp := [1]byte{}
	// 从标准输入获取值,未输入一直处于等待状态
	os.Stdin.Read(tmp[:])
	doneChan <- struct{}{}
}

// 灵活启用指定数量的 goroutine
// n 为要开启的 goroutine 数量
func startWorker(n int, ch chan *item, resultChan chan *result) {
	for i := 0; i < n; i++ {
		go consumer(ch, resultChan)
	}
}

func main() {
	// 通道初始化,结构体指针类型
	itemChan = make(chan *item, 100)
	resultChan = make(chan *result, 100)
	doneChan = make(chan struct{}, 1)
	// 启用生产者 goroutine
	go producer(itemChan)
	// 消费者 goroutine,高并发处理
	startWorker(30, itemChan, resultChan)
	go inputChan(doneChan)
	// 打印结果
	printResult(doneChan, resultChan)
}

八、单向通道

  • 在函数中稚嫩那个发送值而不能接收值为只写通道

  • 只能接收不能发送值称为只读通道

  • 可以让代码意向更明确,更清晰

  • 示例:

package main

import "fmt"

var ch chan int

//只写通道,针对一个函数中实现
func writeCh(ch chan<- int) {
	ch <- 10
}

//只读通道
func read(ch <-chan int) int {
	ret := <-ch
	return ret
}

func main() {
	ch = make(chan int, 1)
	writeCh(ch)
	fmt.Println(read(ch))
}


//输出结果如下
10

总结

出现panic情况:
通道关闭以后,可以取值,但是不能传递值,会出现panic
通道关闭后,如果再次关闭通道,会panic
出现死锁的情况:
通道中元素的容量超出也会死锁
没有传递元素到通道里,直接取值,会死锁

你可能感兴趣的:(Golang,golang,go)