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

2024-03-08 01:20

本文主要是介绍golang 协程 (goroutine) 与通道 (channel),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

golang的协程和通道,之前就看过了,一直没有很好的理解,所以一直也没记录,今天看书,看到有一个总结的章节,里面记录了一些注意事项,因此写个文档,记录一下,避免以后自己忘了或者是找不见资料

顺便吐槽下公司的业务,自己负责的业务能啥也不知道,开发完了给他们上线了,完事还问你,这个为什么会这样,这不是你要求的吗?UAT的时候业务全程参加,都看过了,没问题才上线,过了一个月尽然能忘得一干二净。

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

出于性能考虑建议使用带缓存的通道:
使用带缓存的通道可以很轻易成倍提高它的吞吐量,某些场景其性能可以提高至 10 倍甚至更多。通过调整通道的容量,甚至可以尝试着更进一步的优化其性能。

限制一个通道的数据数量并将它们封装成一个数组:
如果使用通道传递大量单独的数据,那么通道将变成性能瓶颈。然而,将数据块打包封装成数组,在接收端解压数据时,性能可以提高至 10 倍。

现在创建一个带缓存的通道:ch := make(chan type,buf)
(1)如何使用 for 或者 for-range 遍历一个通道:(尽量使用这种或者是跟select配合使用)

这种其实就是一个for循环遍历通道,但是golang的机制,这里会自动监测通道是否关闭,而不需要开发二次判断通道是否关闭
但是这里有个坑需要注意,会有死锁的问题,因为你的通道中没有数据的时候,for range ch 会发生阻塞,但是无法解除阻塞,发生死锁

for v := range ch {// do something with v
}

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

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

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

ch := make(chan int) // Allocate a channel.
// Start something in a goroutine; when it completes, signal on the channel.
go func() {// doSomethingch <- 1 // Send a signal; value does not matter.
}()
doSomethingElseForAWhile()
<-ch // Wait for goroutine to finish; discard sent value.
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 goroutinesdoSomethingElseForAWhile()result := <- ch
}

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

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

(5)通道迭代器模板:

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

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

package mainconst MAXREQS = 50var sem = make(chan int, MAXREQS)type Request struct {a, b   intreplyc chan int
}func process(r *Request) {// do something
}func handle(r *Request) {sem <- 1 // doesn't matter what we put in itprocess(r)<-sem // one empty place in the buffer: the next request can start
}func server(service chan *Request) {for {request := <-servicego handle(request)}
}func main() {service := make(chan *Request)go server(service)
}

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

func DoAll(){sem := make(chan int, NCPU) // Buffering optional but sensiblefor i := 0; i < NCPU; i++ {go DoPart(sem)}// Drain the channel sem, waiting for NCPU tasks to completefor i := 0; i < NCPU; i++ {<-sem // wait for one task to complete}// All done.
}func DoPart(sem chan int) {// do the part of the computationsem <-1 // signal that this piece is done
}func main() {runtime.GOMAXPROCS(NCPU) // runtime.GOMAXPROCS = NCPUDoAll()
}

(8)如何终止一个协程:runtime.Goexit()

(9)简单的超时模板:

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

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

func Worker(in, out chan *Task) {for {t := <-inprocess(t)out <- t}
}

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

// 注意缓冲大小设置为 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 somethingcase <-time.After(timeoutNs):// call timed outbreak L}
}

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

package mainimport ("fmt""time"
)func main() {tick := time.Tick(1e8)boom := time.After(5e8)for {select {case <-tick:fmt.Println("tick.")case <-boom:fmt.Println("BOOM!")returndefault:fmt.Println("    .")time.Sleep(5e7)}}
}

这篇关于golang 协程 (goroutine) 与通道 (channel)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/785493

相关文章

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Golang使用etcd构建分布式锁的示例分享

《Golang使用etcd构建分布式锁的示例分享》在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统,分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要,它有助于维护一致性,防止竞... 目录引言环境准备新建Go项目实现加锁和解锁功能测试分布式锁重构实现失败重试总结引言我们将使用Go作

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

使用协程实现高并发的I/O处理

文章目录 1. 协程简介1.1 什么是协程?1.2 协程的特点1.3 Python 中的协程 2. 协程的基本概念2.1 事件循环2.2 协程函数2.3 Future 对象 3. 使用协程实现高并发的 I/O 处理3.1 网络请求3.2 文件读写 4. 实际应用场景4.1 网络爬虫4.2 文件处理 5. 性能分析5.1 上下文切换开销5.2 I/O 等待时间 6. 最佳实践6.1 使用 as

Go Channel的实现

channel作为goroutine间通信和同步的重要途径,是Go runtime层实现CSP并发模型重要的成员。在不理解底层实现时,经常在使用中对channe相关语法的表现感到疑惑,尤其是select case的行为。因此在了解channel的应用前先看一眼channel的实现。 Channel内存布局 channel是go的内置类型,它可以被存储到变量中,可以作为函数的参数或返回值,它在r

Golang进程权限调度包runtime

关于 runtime 包几个方法: Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行GOMAXPROCS:设置最大的可同时使用的 CPU 核数Goexit:退出当前 goroutine(但是defer语句会照常执行)NumGoroutine:返回正在执行和排队的任务总数GOOS:目标操作系统NumCPU:返回当前系统的 CPU 核数量 p

Golang 网络爬虫框架gocolly/colly(五)

gcocolly+goquery可以非常好地抓取HTML页面中的数据,但碰到页面是由Javascript动态生成时,用goquery就显得捉襟见肘了。解决方法有很多种: 一,最笨拙但有效的方法是字符串处理,go语言string底层对应字节数组,复制任何长度的字符串的开销都很低廉,搜索性能比较高; 二,利用正则表达式,要提取的数据往往有明显的特征,所以正则表达式写起来比较简单,不必非常严谨; 三,使