开发go程序的时候,时常需要使用goroutine并发处理任务,有时候这些goroutine是相互独立的,而有的时候,多个goroutine之间常常是需要同步与通信的。那么Golang中控制并发的方法,主要有哪几类呢?大致可以分为三类:
package main
import (
"fmt"
"time"
)
func main() {
running := true
f := func() {
for running {
fmt.Println("sub proc running...")
time.Sleep(1 * time.Second)
}
fmt.Println("sub proc exit")
}
go f()
go f()
go f()
time.Sleep(2 * time.Second) // sleep 2秒,让子goroutine运行一会
running = false // running置为false,告诉子gorroutine该退出了
time.Sleep(3 * time.Second)
fmt.Println("main proc exit")
}
优势
全局变量的优势是简单方便,不需要过多繁杂的操作,通过一个变量就可以控制所有子goroutine的开始和结束;
对于1中的场景:主goroutine中通知子goroutine停止运行,使用channel是一种更优雅的实现方式。代码也比较简单,代码如下:
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
)
func consumer(stop <-chan bool, i int) {
for {
select {
case msg, ok := <-stop:
if !ok {
fmt.Println("channel stopped, exit sub goroutine, msg: ", msg)
}
return
default:
fmt.Println(i, " running...")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
stop := make(chan bool)
var wg sync.WaitGroup
// Spawn example consumers
for i := 0; i < 3; i++ {
wg.Add(1)
go func(stop <-chan bool, i int) {
defer wg.Done()
consumer(stop, i)
}(stop, i)
}
waitForSignal()
close(stop)
fmt.Println("stopping all jobs!")
wg.Wait()
}
func waitForSignal() {
sigs := make(chan os.Signal)
signal.Notify(sigs, os.Interrupt)
signal.Notify(sigs, syscall.SIGTERM)
<-sigs
}
Context的创建和调用关系是层层递进的,也就是我们通常所说的链式调用,类似数据结构里的树,从根节点开始,每一次调用就衍生一个叶子节点。首先,生成根节点,使用context.Background方法生成,而后可以进行链式调用使用context包里的各类方法,context包里的所有方法:
想要了解更多有关context的东西,可以直接去看源码,这里有有一篇很不错的源码剖析帖:context源码剖析,这里仅以WithCancel和WithValue方法为例来实现控制并发和通信,代码如下:
package main
import (
"context"
"crypto/md5"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
)
type favContextKey string
func main() {
wg := &sync.WaitGroup{}
values := []string{"https://www.baidu.com/", "https://www.zhihu.com/"}
ctx, cancel := context.WithCancel(context.Background())
for _, url := range values {
wg.Add(1)
subCtx := context.WithValue(ctx, favContextKey("url"), url)
go reqURL(subCtx, wg)
}
go func() {
time.Sleep(time.Second * 3)
cancel()
}()
wg.Wait()
fmt.Println("exit main goroutine")
}
func reqURL(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
url, _ := ctx.Value(favContextKey("url")).(string)
for {
select {
case <-ctx.Done():
fmt.Printf("stop getting url:%s\n", url)
return
default:
r, err := http.Get(url)
if r.StatusCode == http.StatusOK && err == nil {
body, _ := ioutil.ReadAll(r.Body)
subCtx := context.WithValue(ctx, favContextKey("resp"), fmt.Sprintf("%s%x", url, md5.Sum(body)))
wg.Add(1)
go showResp(subCtx, wg)
}
r.Body.Close()
//启动子goroutine是为了不阻塞当前goroutine,这里在实际场景中可以去执行其他逻辑,这里为了方便直接sleep一秒
// doSometing()
time.Sleep(time.Second * 1)
}
}
}
func showResp(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("stop showing resp")
return
default:
//子goroutine里一般会处理一些IO任务,如读写数据库或者rpc调用,这里为了方便直接把数据打印
fmt.Println("printing ", ctx.Value(favContextKey("resp")))
time.Sleep(time.Second * 1)
}
}
}
参考资料:
https://www.jianshu.com/p/6e40d6a5db52
https://studygolang.com/articles/9532
https://leileiluoluo.com/posts/golang-channels.html
https://www.jianshu.com/p/0514c4ba464c