golang 协程 (goroutine) 与通道 (channel)

golang的协程和通道,之前就看过了,一直没有很好的理解,所以一直也没记录,今天看书,看到有一个总结的章节,里面记录了一些注意事项,因此写个文档,记录一下,避免以后自己忘了或者是找不见资料
顺便吐槽下公司的业务,自己负责的业务能啥也不知道,开发完了给他们上线了,完事还问你,这个为什么会这样,这不是你要求的吗?UAT的时候业务全程参加,都看过了,没问题才上线,过了一个月尽然能忘得一干二净。
出于性能考虑的建议:

实践经验表明,为了使并行运算获得高于串行运算的效率,在协程内部完成的工作量,必须远远高于协程的创建和相互来回通信的开销。
出于性能考虑建议使用带缓存的通道:

使用带缓存的通道可以很轻易成倍提高它的吞吐量,某些场景其性能可以提高至 10 倍甚至更多。通过调整通道的容量,甚至可以尝试着更进一步的优化其性能。
限制一个通道的数据数量并将它们封装成一个数组:

如果使用通道传递大量单独的数据,那么通道将变成性能瓶颈。然而,将数据块打包封装成数组,在接收端解压数据时,性能可以提高至 10 倍。
现在创建一个带缓存的通道:ch := make(chan type,buf)

(1)如何使用 for 或者 for-range 遍历一个通道:(尽量使用这种或者是跟select配合使用)
这种其实就是一个for循环遍历通道,但是golang的机制,这里会自动监测通道是否关闭,而不需要开发二次判断通道是否关闭
但是这里有个坑需要注意,会有死锁的问题,因为你的通道中没有数据的时候,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁

go 复制代码
for v := range ch {
    // do something with v
}

(2)如何检测一个通道 ch 是否关闭:

go 复制代码
//read channel until it closes or error-condition
for {
    if input, open := <-ch; !open {
        // 这里!open,就是表示通道已经被关了,break跳出循环,不从通道里面获取数据了
        break
    }
    fmt.Printf("%s", input)
}

(3)如何通过一个通道让主程序等待直到协程完成(信号量模式):如果希望程序一直阻塞,在匿名函数中省略 ch <- 1 即可。

go 复制代码
ch := make(chan int) // Allocate a channel.
// Start something in a goroutine; when it completes, signal on the channel.
go func() {
    // doSomething
    ch <- 1 // Send a signal; value does not matter.
}()
doSomethingElseForAWhile()
<-ch // Wait for goroutine to finish; discard sent value.
go 复制代码
func compute(ch chan int){
	ch <- someComputation() // when it completes, signal on the channel.
}

func main(){
	ch := make(chan int) 	// allocate a channel.
	go compute(ch)		// start something in a goroutines
	doSomethingElseForAWhile()
	result := <- ch
}

(4)通道的工厂模板:以下函数是一个通道工厂,启动一个匿名函数作为协程以生产通道:

go 复制代码
func pump() chan int {
    ch := make(chan int)
    go func() {
        for i := 0; ; i++ {
            ch <- i
        }
    }()
    return ch
}

(5)通道迭代器模板:

go 复制代码
func (c *container) Iter () <- chan item {
	ch := make(chan item)
	go func () {
		for i:= 0; i < c.Len(); i++{	// or use a for-range loop
			ch <- c.items[i]
		}
	} ()
	return ch
}
go 复制代码
for x := range container.Iter() { ... }

(6)如何限制并发处理请求的数量

go 复制代码
package main

const MAXREQS = 50

var sem = make(chan int, MAXREQS)

type Request struct {
	a, b   int
	replyc chan int
}

func process(r *Request) {
	// do something
}

func handle(r *Request) {
	sem <- 1 // doesn't matter what we put in it
	process(r)
	<-sem // one empty place in the buffer: the next request can start
}

func server(service chan *Request) {
	for {
		request := <-service
		go handle(request)
	}
}

func main() {
	service := make(chan *Request)
	go server(service)
}

(7)如何在多核CPU上实现并行计算:

go 复制代码
func DoAll(){
    sem := make(chan int, NCPU) // Buffering optional but sensible
    for i := 0; i < NCPU; i++ {
        go DoPart(sem)
    }
    // Drain the channel sem, waiting for NCPU tasks to complete
    for i := 0; i < NCPU; i++ {
        <-sem // wait for one task to complete
    }
    // All done.
}

func DoPart(sem chan int) {
    // do the part of the computation
    sem <-1 // signal that this piece is done
}

func main() {
    runtime.GOMAXPROCS(NCPU) // runtime.GOMAXPROCS = NCPU
    DoAll()
}

(8)如何终止一个协程:runtime.Goexit()
(9)简单的超时模板:

go 复制代码
timeout := make(chan bool, 1)
go func() {
    time.Sleep(1e9) // one second  
    timeout <- true
}()
select {
    case <-ch:
    // a read from ch has occurred
    case <-timeout:
    // the read from ch has timed out
}

(10)如何使用输入通道和输出通道代替锁:

go 复制代码
func Worker(in, out chan *Task) {
    for {
        t := <-in
        process(t)
        out <- t
    }
}

(11)如何在同步调用运行时间过长时将之丢弃:

go 复制代码
// 注意缓冲大小设置为 1 是必要的,可以避免协程死锁以及确保超时的通道可以被垃圾回收。
// 此外,需要注意在有多个 case 符合条件时, select 对 case 的选择是伪随机的
// 如果代码稍作修改如下
// 则 select 语句可能不会在定时器超时信号到来时立刻选中 time.After(timeoutNs) 对应的 case
// 因此协程可能不会严格按照定时器设置的时间结束。
ch := make(chan int, 1)
go func() { for { ch <- 1 } } ()
L:
for {
    select {
    case <-ch:
        // do something
    case <-time.After(timeoutNs):
        // call timed out
        break L
    }
}

(12)如何在通道中使用计时器和定时器:定时器 (Timer) 结构体和计时器 (Ticker) 结构体

go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(1e8)
	boom := time.After(5e8)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(5e7)
		}
	}
}
相关推荐
zmd-zk18 分钟前
flink学习(3)——方法的使用—对流的处理(map,flatMap,filter)
java·大数据·开发语言·学习·flink·tensorflow
圆蛤镇程序猿22 分钟前
【什么是SpringMVC】
开发语言
Domain-zhuo40 分钟前
JS对于数组去重都有哪些方法?
开发语言·前端·javascript
stormsha1 小时前
go-rod vs Selenium:自动化测试工具的比较与选择
python·selenium·测试工具·golang
逝去的紫枫1 小时前
Python Selenium:Web自动化测试与爬虫开发
开发语言·python·selenium
SUN_Gyq1 小时前
什么是 C++ 中的模板特化和偏特化? 如何进行模板特化和偏特化?
开发语言·c++·算法
Coderfuu1 小时前
Java技术复习提升 10异常
java·开发语言
淡写青春2091 小时前
计算机基础---进程间通信和线程间通信的方式
java·开发语言·数据结构
《源码好优多》1 小时前
基于Java Springboot未央商城管理系统
java·开发语言·spring boot
平头哥在等你1 小时前
python特殊字符序列
开发语言·python·正则表达式