c++ map用法_go channel的高级用法

上篇文章go 协程,我们介绍了go协程以及channel的简单用法,这篇我们详细介绍go channel的一些高级用法。

通道选择器select

说起channal的高级用法,我们必须先介绍下通道选择器select,它让你同时等待多个通道操作, 类似于用于通信的 switch 语句。 每个 case 必须是一个通信操作,要么是发送要么是接收。 select 随机执行一个可运行的 case。 如果没有 case 可运行,它将阻塞,直到有 case 可运行。 一个默认的子句应该总是可运行的。
/* 语法如下如果任意某个通信可以进行,它就执行,其他被忽略如果有多个case都可以运行,select会随机地选出一个执行。其他不会执行如果没有case可以运行:  - 如果有default子句,则执行该语句  - 如果没有default子句,select将阻塞,直到某个通信可以运行*/select {    case channel1:        exec1()    case channel2:        exec2()    // 可以定义任意数量的 case    default : // 可选         execn()}

    与select配合,可以产生很多高级用法。

1,协程间同步等待

默认通道是无缓冲的,发送和接收操作是阻塞的,直到发送方和接收方都准备完毕,我们可以利用这一特性,实现协程间的同步等待。
package mainimport "fmt"// ch1 := make(chan string) // 默认通道,无缓冲// 可缓存通道允许在没有对应接收方的情况下,缓存限定数量的值// ch2 := make(chan string, 2) // 缓冲通道func main() {  ch := make(chan string) // 创建channel  go func(ch chan string) { // 启动协程异步执行任务    for i := 0; i < 3; i++ {      fmt.Println(i)    }    ch "done"   }(ch)  // 阻塞等待任务完成  fmt.Println("task done")}

2,超时处理

package mainimport (  "fmt"  "time")func main() {  ch1 := make(chan string) // 创建channel1  ch2 := make(chan string) // 创建channel12  go func(ch chan string) { // 启动协程1    time.Sleep(time.Second * 1)    ch "one"  }(ch1)  go func(ch chan string) { // 启动协程2    time.Sleep(time.Second * 2)    ch "two"  }(ch2)  timeout := false  for !timeout {    select {    case msg :=       fmt.Printf("received msg: %s\n", msg)    case msg :=       fmt.Printf("received msg: %s\n", msg)    case 5):       fmt.Printf("timeout and exit\n")      timeout = true    }  }}

输出如下

b1fd8fb9434609a48b335587ee71b4a0.png

3,简单的crontab定时操作

package mainimport (  "fmt"  "time")func main() {  timer1 := time.NewTimer(time.Second * 3) // 创建一次性定时器,3秒后到期  go func() {    // 使用定时器的通道进行阻塞    fmt.Println("start doing something")  }()  timer2 := time.NewTicker(time.Second * 2) // 创建周期性定时器,每隔2s执行一次  go func() {    for t := range timer2.C { // 遍历定时器的通道      fmt.Println("Tick at", t)    }  }()  10) }

输出如下

c19dda88174bc61fa79f973ecfe70da8.png

    我们来看下time.NewTimer,time.NewTicker的源码

deed10dedf0bd5f93e630a1f93466fdf.png

371d0ae41b6724203d734312f2e665e2.png

    都用到了通道,区别在于runtimeTimer{}结构体 period字段上,详细源码请大家自行研究。

4,多协程工作池

实现一个类似python multiprocessing.Pool的功能以下为python相关代码
# 使用多线程池from multiprocessing.dummy import Poolimport timedef add(item):    # sleep 1ms    time.sleep(0.001)    return item[0] + item[1]if __name__ == "__main__":    st = time.time() * 1000    items = []    for i in range(100000):        items.append((i, i))    p = Pool(100) # 创建线程池    results = p.map(add, items) # 并发执行add函数    et = time.time() * 1000    # 打印程序执行到此处的时间耗时    print("cost: %s ms" % (et - st))    for res in results:        print(res)

以下为go 实现的同等功能代码,这里输入列表,执行函数没有支持泛型,使用了固定的类型,函数(这点后续再优化,主要是使用channel实现并发工作池)

package mainimport (  "errors"  "fmt"  "sort"  "time")// 定义函数类型type Func func(int, int) int// 输入参数结构体type In struct {  id int //序号  a  int  b  int}// 输出结构体type Out struct {  id  int //序号  res int}// 结果列表,实现Len,Less, Swap函数,用于此结构体列表排序type Outs []Outfunc (self Outs) Len() int {  return len(self)}func (self Outs) Less(i, j int) bool {  return self[i].id < self[j].id}func (self Outs) Swap(i, j int) {  self[i], self[j] = self[j], self[i]}// 协程池结构体type Pool struct {  cIn    chan In  cOut   chan Out  goNum  int  handle Func}// 创建协程池函数func NewPool(num int) Pool {  pool := Pool{}  pool.goNum = num  return pool}/*map函数,并发执行handle函数,结果是按照输入顺序排序的 */func (self Pool) Map(handle Func, items []In) (Outs, error) {  if len(items) == 0 {    return nil, errors.New("items lengths is 0")  }  self.cIn = make(chan In, len(items)) //输入通道  self.cOut = make(chan Out, len(items)) //输出通道  self.handle = handle  // 启动工作协程池  for i := 0; i < self.goNum; i++ {    go self.Worker(i)  }  // 输入列表发送到输入通道,触发协程工作  for _, item := range items {    self.cIn     fmt.Printf("send %v to cIn\n", item)  }  close(self.cIn)  finishNum := 0  var result Outs  for {    // finish个数与输入个数一样时,退出循环    if finishNum == len(items) {      break    }    val, ok := // 获取输出结果    // fmt.Println(val.id, val.res)    if ok {      result = append(result, val)      finishNum ++    }  }  // 对结果按照序号排序  sort.Sort(result)  return result, nil}func (self Pool) Worker(thId int) {  for cIn := range self.cIn {    fmt.Printf("thId: %d get In %v\n", thId, cIn)    res := self.handle(cIn.a, cIn.b)    self.cOut   }}func add(a, b int) int {  // sleep 1ms  time.Sleep(time.Millisecond)  return a + b}func main() {  startTime := time.Now()  items := []In{}  for i := 0; i < 100000; i++ {    items = append(items, In{i, i, i})  }  pool := NewPool(100)  // 类似python的map函数  outs, err := pool.Map(add, items)  // 打印程序执行到这里的时间耗时  fmt.Println("cost", time.Since(startTime))  if err != nil {    fmt.Println("error:", err)  } else {    for _, out := range outs {      fmt.Println(out.id, out.res)    }  }}
以上就是使用channel实现工作池的代码,可以看出python的代码是多么的简洁,也就20行代码。我们对比下两者的性能

10万个元素,100个工作线程对比结果如下

fa3c07cdda09df048e223d5ba3d8a91a.png go性能是python的2倍,当然由于python GIL的原因,Python的多线程是伪多线程。

我们将工作线程的个数提升到1000个,go代码时间耗时146ms,python5分钟之内没有跑出结果,同时我的笔记本风扇开始工作了7a2caddcd3107648d5ea3066ed1db0a7.png。这就是go一个线程可以同时跑成千上万个协程的好处了,当然协程数量越多也不是性能越快,太多协程底层锁竞争就会成为瓶颈。

5,实现互斥锁

可以用channel实现互斥锁,当然内建互斥锁效率更高

package mainimport (  "fmt"  "sync")type Lock struct {  c chan struct{}}func NewLock() Lock {  return Lock{c: make(chan struct{}, 1)}}func (self Lock) Lock() {  self.c struct{}{}}func (self Lock) UnLock() {  self.}func main() {  wg := sync.WaitGroup{}  lock := NewLock()  count := 0  for i := 0; i < 1000; i++ {    wg.Add(1)    go func() {      lock.Lock()      count += 1      lock.UnLock()      wg.Done()    }()  }  wg.Wait()  fmt.Println(count)}

channel的高级用法今天就先讲到这f87d66c4cf9a74c4e056762fc56623e2.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值