Go语言并发之道--笔记2

池:
池(Pool)是Pool模式的并发安全实现
Pool模式是一种创建和提供可供使用的固定数量实例或Pool实例的方法
Pool的主接口方法是它的Get方法
示例:
myPool := &sync.Pool{
   new: func() interface{} {
      fmt.Println("Creating new instance.")
      return struct{}{}
   },
}
myPool.Get()   //调用Pool的get方法,调用将执行Pool中定义的new函数,因为实例还没有实例化
instance := myPool.Get()   //同上
myPool.Put(instance)   //将先前检索到的实例放在池中,增加了实例可用数量
myPool.Get()   //执行此调用,将重用以前分配的实例并将其放回池中,New将不会被调用


var numCalcsCreated int
calcPool := &sync.Pool {
   New: func() interface{} {
      numCalcsCreated += 1
      mem := make([]byte,1024)
      return &mem       //正在存储bytes切片的地址
   },
}

//使用4KB初始化pool
calcPool.Put(calcPool.New())
calcPool.Put(calcPool.New())
calcPool.Put(calcPool.New())
calcPool.Put(calcPool.New())

const numWorkers = 1024*1024
var wg sync.WaitGroup
wg.Add(numWorkers)
for i := numWorkers; i > 0;i -- {
   go func() {
      defer wg.Done()
      mem := calcPool.Get().(*[]byte)       //断言类型是一个指向bytes切片的指针
      defer calcPool.Put(mem)

      //做一些有趣的假设,但是很快就会用这个内存完成
   }()
}

wg.Wait()
fmt.Printf("%d calculators were created.",mumCalcsCreated)


用Pool来尽可能快地将预先分配地对象缓存加载启动
不是试图通过该限制创建地对象地数量来节省主机地内存,而是通过提前加载获取引用到另一个对象所需地时间,来节省消费者地时间
示例对比:
//创建一个模拟创建到服务的连接的函数,连接花费很长时间
func connectToService() interface{} {
   time.Sleep(1*time.Second)
   return struct{}{}
}
//编写一个网络处理程序,为每一个请求都打开一个新的连接
func startNetworkDaemon() *sync.WaitGroup {
   var wg sync.WaitGroup
   wg.Add(1)
   go func() {
      server,err := net.Listen("tcp","localhost:8080")
      if err != nul {
         log.Fatalf("cannot listen: %v",err)
      }
      defer server.Close()

      wg.Done()

      for {
         conn,err := server.Accept()
         if err != nil{
            log.Printf("cannot accept connection: %v",err)
            continue
         }
         connectToService()
         fmt.Fprintln(conn,"")
         conn.Close()
      }
   }()
   return &wg
}
//基准及测试方法
func init() {
   daemonStarted := startNetworkDaemon()
   daemonStarted.Wait()
}

func BenchmarkNetworkRequest(b *testing.B) {
   for i:=0;i<b.N;i++ {
      conn,err := net.Dial("tcp","localhost:8080")
      if err!=nil {
         b.Fatalf("cannot dial host: %v",err)
      }
      if _,err := ioutil.ReadAll(conn);err != nil {
         b.Fatalf("cannot read: %v",err)
      }
      conn.Close()
   }
}

//测试
go test -benchtime=10s -bench=.


使用sync.Pool来改进上面的虚拟服务
func warmServiceConnCache() *sync.Pool {
   p := &sync.Pool {
      New: connectToService,
   }
   for i:=0;i<10;i++ {
      p.Put(p.New())
   }
   return p
}

func startNetworkDaemon() *sync.WaitGroup {
   var wg sync.WaitGroup
   wg.Add(1)
   go func() {
      connPool := warmServiceConnCache()
      server,err := net.Listen("tcp","localhost:8080")
      if err != nil {
         log.Fatalf("cannot listen: %v",err)
      }
      defer server.Close()

      wg.Done()

      for {
         conn,err := server.Accept()
         if err != nil {
            log.Printf("cannot accept connection: %v",err)
            continue
         }
         svcConn := connPool.Get()
         fmt.Fprintln(conn,"")
         connPool.Put(svcConn)
         conn.Close()
      }
   }()
   return &wg
}
//进行基础测试
go test -benchtime=10s -bench=.

并发进程需要请求一个对象,但是在实例化之后很快的处理他们,或者在这些对象的构造可能会对内存产生负面影响,这时最好使用Pool设计模式
如果你使用 Pool 代码所需要的东西不是大概同质的,那么从 Pool 转化检索到所需要的内容的时间可能比重新实例化内容要花费的时间更多,
例如,程序需要随机和可变长度的切片,不建议使用Pool

使用Pool时注意事项:
1、当实例化sync.Pool,使用new方法创建一个成员变量,在调用时是线程安全的
2、当收到一个来自Get的实例时,不要对所接收的对象的状态做出任何假设
3、当用完了一个从Pool中取出的对象时,一定调用Put,否则,Pool就无法复用这个实例,通常情况,是用defer完成的
4、Pool内的分布必须大致均匀



channel
可以用来goroutine之间的信息传递,同步内存访问

//声明一个channel,声明的类型是空接口,因此它的类型是interface{}
var dataStream chan interface{}
//使用内置的make函数实例化channel
dataStream = make(chan interface{})

//声明和实例化一个只能读取的channel
var dataStream <-chan interface{}
dataStream := make(<-chan interface{})

//声明和实例化一个只能发送的channel
var dataStream chan<- interface{}
dataStream := make(chan<- interface{})

//GO语言隐式地将双向channel转换为单向channel
var receiveChan <-chan interface{}
var sendChan chan<- interface{}
dataStream := make(chan interface{})
//转换,有效地语法
receiveChan = dataStream
sendChan = dataStream

//整数channel地例子
intStream := make(chan int)

示例:
stringStream := make(chan string)
go func() {
   stringStream <- "Hello channels!"  //将字符串文本传递到 stringStream channel
}()
fmt.Println(<-stringStream)    //读取channel 地字符串字面量并将其打印到 stdout

//使用close关键字关闭一个channel
valueStream := make(chan interface{})
close(valueStream)

//从关闭地channel中读取数据
valueStream := make(chan interface{})
close(valueStream)
integer,ok := <-valueStream    //从已经关闭地数据流中读取数据
fmt.Printf("(%v): %v",ok,integer)

//通过range关键作为参数遍历(与for语句一起使用),并且在channel关闭时自动中断循环
intStream := make(chan int)
go func() {
   defer close(intStream) //确保在goroutine退出之前channel是关闭地,常见地模式
   for i:=1;i<=5:i++ {
      intStream <- i
   }
}()

for integer := range intStream {   //遍历intStream
   fmt.Printf("%v",integer)
}
//range方法不会返回第二个布尔值,处理一个已关闭地channel地细节可以让你保持循环简洁
//关闭channel也是一种同时给多个goroutine发信号地方法


示例:
//在同一时间,同时打开多个goroutine
begin := make(chan interface{})
var wg sync.WaitGroup
for i:=0;i<5;i++ {
   wg.Add(1)
   go func(i int) {
      defer wg.Done()
      <-begin    //goroutine会一直等待,知道它被告知可以继续运行
      fmt.Printf("%v has begun\n",i)
   }(i)
}
fmt.Println("Unblocking goroutines...")
close(begin)   //关闭channel,从而同时打开所有的goroutine
wg.Wait()


//创建buffered channel
//创建一个有4个容量的缓冲channel,可以把4个东西放到channel上,不用管它是否能被读取
var dataStream chan interface{}
dataStream = make(chan interface{},4)

缓冲channel是一个内存中的FIFO队列,用于并发进程进行通信

示例:
var stdoutBuffer bytes.Buffer  //创建一个内存缓冲区,减少输出的不确定性,
defer stdoutBuffer.WriteTo(os.Stdout)  //确保在进程退出之前缓冲区的内容需要被写入到stdout

intStream := make(chan int,4)  //创建一个具有一个容量的缓冲channel
go func() {
   defer close(intStream)
   defer fmt.Fprintln(&stdoutBuffer,"Producer Done.")
   for i:=0;i<5;i++ {
      fmt.Fprintf(&stdoutBuffer,"Sending: %d\n",i)
      intStream <- i
   }
}()

for integer := range intStream {
   fmt.Fprintf(&stdoutBuffer,"Received %v.\n",integer)
}



创建一个拥有channel的groutine,以及一个处理channel阻塞和关闭的消费者
chanOwner := func() <-chan int {
   resultStream := make(chan int,5)   //实例化一个缓冲channel,数量为5
   go func() {    //启动一个匿名的goroutine,在resultStream上执行写操作
      defer close(resultStream)  //确保执行完resultStream就会关闭
      fo i:=0;i<=5;i++ {
         resultStream <- i
      }
   }()
   return resultStream    //返回channel,返回值被声明为一个只读channel,resultStream将隐式地转换为只读消费者
}

resultStream := chanOwner()
for result := range resultStream { //遍历resultStream
   fmt.Printf("Received: %d\n",result)
}
fmt.Println("Done receiving!")



select 语句
start := time.Now()
c := make(chan interface{})
go func() {
   time.Sleep(5*time.Second)
   close(c)   //等待5s后关闭channel
}()

fmt.Println("Blocking on read...")
select {
   case <-c:  //尝试在channel上读取数据
      fmt.Printf("Unblocked %v later.\n",time.Since(start))
}



GOMAXPROCS 控制



Go语言地并发模式
约束
不同的保证操作安全的方法:
用于共享内存的同步原语(如sync.Mutex)
通过通信共享内存来进行同步(如channel)

隐式并发安全方法:
不会发生改变数据
受到保护的数据

约束是一种确保了信息只能从一个并发过程中获取到的简单且强大的方法
特定约束
词法约束

特定约束示例:
data := make([]int,4)

loopData := func(handleData chan<- int) {
   defer close(handleData)
   for i := range data {
      handleData <- data[i]
   }
}

handleData := make(chan int)
go loopData(handleData)

for num := range handleData {
   fmt.Println(num)
}


词法约束示例:
chanOwner := func() <-chan int {
   results := make(chan int,5)    //在chanOwner函数词法范围类实例化channel
   go func() {
      defer close(results)
      for i:=0;i<=5;i++ {
         results <- i
      }
   }()
   return results
}

consumer := func(results <-chan int) { //收到一个int channel的只读副本,通过声明要求的唯一用法是读访问,将channel内的使用约束为只读
   for result := range results {
      fmt.Printf("Received: %d\n",result)
   }
   fmt.Println("Done receiving!")
}

results := chanOwner() //收到channel的读处理,将其传递给消费者
consumer(results)



使用不是并发安全的数据结构的约束的例子,bytes.Buffer
printData := func(wg *sync.WaitGroup,data []byte) {
   defer wg.Done()

   var buffer bytes.Buffer
   for _,b:=range data {
      fmt.Fprintf(&buffer,"%c",b)
   }
   fmt.Println(buffer.String)
}

var wg sync.WaitGroup
wg.Add(2)
data := []byte("golang")
go printData(&wg,data[:3])
go printData(&wg,data[3:])

wg.Wait()



for-select 循环
向channel发送迭代变量:
将可迭代的内容转换为channel上的值:
for _,s := range []string{"a","b","c"} {
   select {
      case <-done:
         return
      case stringStream <- s:
   }
}

循环等待停止:
保持select语句尽可能短:
如果已经完成的channel未关闭,将退出select语句并继续执行for循环的其余部分
for {
   select {
      case <-done:
         return
      default:
   }
}

将工作嵌入到选择语句的默认子句中
当我们输入select语句时,如果完成的channel尚未关闭,将执行default子句
for {
   select {
      case <-done:
         return
      default:
         //进行非抢占式任务
   }
}


防止goroutine泄露
goroutine不会被运行时垃圾回收
goroutine被终止的方式:
完成工作
不可恢复的错误导致不能继续工作
被告知需要终止工作

一个简单的goroutine泄露:
   doWork := func(strings <-chan string) <-chan interface{} {
      completed := make(chan interface{})
      go func() {
         defer fmt.Println("doWork exited.")
         defer close(completed)
         for s := range strings {
            //做一些有趣的操作
            fmt.Println(s)
         }
      }()
      return completed
   }

   doWork(nil)
   //也许这里有其他操作需要进行
   fmt.Println("Done.")

将一个空的channel传给doWork,字符串channel永远不会写入任何字符串,包含doWork的goroutine将在此过程的整个生命周期中保留内存中


父goroutine将名为Done的只读channel传递给子goroutine,若想要取消子goroutine时关闭该channel
doWork := func(
   done <-chan interface{},
   strings <-chan string,
) <-chan interface{} { //将完成的channel传递给doWork函数
   terminated := make(chan interface{})
   go func() {
      defer fmt.Println("doWork exited.")
      defer close(terminated)
      for {
         select {
            case <-done:   //检查done channel是否已经发出信号,如有,从goroutine返回
               return
         }
      }
   }()
   return terminated
}

done := make(chan interface{})
terminated := doWork(done,nil)

go func() {    //创建另一个goroutine,若超过1s就会取消doWork中产生的goroutine
   //在1秒钟之后取消本操作
   time.Sleep(1 * time.Second)
   fmt.Println("Canceling doWork goroutine...")
   close(done)
}()

<-terminated   //加入从main goroutine的doWork中产生的goroutine位置
fmt.Println("Done.")



处理一个goroutine阻塞向channel进行写入的请求:
newRandStream := func() <-chan int {
   randStream := make(chan int)
   go func() {
      //无法告诉生产者可以停止,所以该语句永远不会运行
      defer fmt.Println("newRandStream closure exited.") //在goroutine成功终止时打印出一条消息

      defer close(randStream)
      for {
         randStream <- rand.Int()   // math/rand包
      }
   }()
   return randStream
}
randStream := newRandStream()
fmt.Println("3 random ints:")
for i:=1;i<=3;i++ {
   fmt.Printf("%d: %d\n",i,<-randStream)
}


为生产者goroutine提供一个通知退出的channel
newRandStream := func(done <-chan interface{}) <-chan int {
   randStream := make(chan int)
   go func() {
      defer fmt.Println("newRandStream closure exited.")
      defer close(randStream)
      for {
         select {
            case randStream <- rand.Int():
            case <-done:
               return
         }
      }
   }()
   return randStream
}

done := make(chan interface{})
randStream := newRandStream(done)
fmt.Println("3 random intes:")
for i:=1;i<=3;i++ {
   fmt.Printf("%d:%d\n",i,<-randStream)
}
close(done)

//模拟正在运行的工作
time.Sleep(1 * time.Second)


如果goroutine负责创建goroutine,则也负责确保它可以停止goroutine


or-channel
将一个或多个完成的channel合并到一个完成的channel中,该channel在任何组件channel关闭时关闭

通过递归和goroutine创建一个符合done channel
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{} { //有函数,或采用可变的channel切片并返回单个channel
   switch len(channels) {
      case 0:       //设置终止标准,切片空,返回空channel,
         return nil
      case 1:       //终止标准,切片只有一个元素,只返回该元素
         return channels[0]
   }

   orDone := make(chan interface{})
   go func() {       //函数主体,递归位置,创建一个goroutine,以便可以不受阻塞的等待channel上消息
      defer close(orDone)

      switch len(channels) {
         case 2:       //基于迭代方式,每次迭代调用至少有两个channel,此处为需要两个channel的情况采用约束goroutine数据的优化方法
            select {
               case <-channels[0]:
               case <-channels[1]:
            }
         default:      //循环到存放所有channel的slice第三个索引,创建一个or-channel并从此channel中选择一个。。。。。。
            select {
               case <-channels[0]:
               case <-channels[1]:
               case <-channels[2]:
               case <-or(append(channels[3:],orDone)...):
            }
      }
   }()
   return orDone
}

//可以将任意数量的channel组合到单个channel中,只要任何组件channel关闭或写入,该channel就会关闭
//经过一段时间后关闭的channel,并将这些channel合并到一个关闭的单个channel中
sig := func(after time.Duration) <-chan interface{}{   //创建一个channel,当后续时间中指定的时间结束时将关闭该channel
   c := make(chan interface{})
   go func() {
      defer close(c)
      time.Sleep(after)
   }()
   return c
}
start := time.Now()    //大致追踪来自or函数的channel何时开始阻塞
<-or(
   sig(2*time.Hour),
   sig(5*time.Minute),
   sig(1*time.Second),
   sig(1*time.Hour),
   sig(1*time.Minute),
)
fmt.Printf("done after %v",time.Since(start))  //打印读取发生的时间



错误处理
示例1:
checkStatus := func(
   done <-chan interface{},
   urls ...string,
) <-chan *http.Response {
   response := make(chan *http.Response)
   go func() {
      defer close(response)
      for _,url := range urls{
         resp,err := http.Get(url)
         if err != nil {
            fmt.Println(err)
            continue
         }
         select {
            case <-done:
               return
            case response <- resp:
         }
      }
   }()
   return response
}
done := make(chan interface{})
defer close(done)
urls := []string{"https://www.google.com","https://badhost"}
for response := range checkStatus(done,urls...) {
   fmt.Printf("Response: %v\n",response.Status)
}


示例2:
type Result struct {   //创建一个包含* http.Response和从goroutine中的循环迭代中可能出现的错误的类型
   Error error
   Response *http.Response
}
checkStatus := func(done <-chan interface{},urls ...string)<-chan Result { //返回一个可读取的channel,以检索循环迭代的结果
   results := make(chan Result)
   go func() {
      defer close(results)
      for _,url := range urls {
         var result Result
         resp,err := http.Get(url)
         result = Result{Error: err,Response: resp} //创建一个Result实例,并设置错误和响应字段
         select {
            case <-done:
               return
            case results <- result:    //将结果写入channel的位置
         }
      }
   }()
   return results
}
done := make(chan interface{})
defer close(done)
urls := []string{"https://www.google.com","https://badhost"}
for result := range checkStatus(done,urls...) {
   if result.Error != nil {   //在main goroutine中,能够智能的处理由checkStatus启动的goroutine中出现的错误,以及更大程序的完整背景
      fmt.Printf("error: %v",result.Error)
      continue
   }
   fmt.Printf("Response: %v\n",result.Response.Status)
}


实例3:
当出现三个或更多错误时停止尝试检查状态
type Result struct {   //创建一个包含* http.Response和从goroutine中的循环迭代中可能出现的错误的类型
   Error error
   Response *http.Response
}
checkStatus := func(done <-chan interface{},urls ...string)<-chan Result { //返回一个可读取的channel,以检索循环迭代的结果
   results := make(chan Result)
   go func() {
      defer close(results)
      for _,url := range urls {
         var result Result
         resp,err := http.Get(url)
         result = Result{Error: err,Response: resp} //创建一个Result实例,并设置错误和响应字段
         select {
            case <-done:
               return
            case results <- result:    //将结果写入channel的位置
         }
      }
   }()
   return results
}

done := make(chan interface{})
defer close(done)

errCount := 0
urls := []string{"a","https://www.google.com","b","c","d"}
for result := range checkStatus(done,urls...) {
   if result.Error != nil {
      fmt.Printf("error: %v\n",result.Error)
      errCount++
      if errCount >= 3 {
         fmt.Println("Too many errors,breaking!")
         break
      }
      continue
   }
   fmt.Printf("Response: %v\n",result.Response.Status)
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书作者带你一步一步深入这些方法。你将理解 Go语言为何选定这些并发模型,这些模型又会带来什么问题,以及你如何组合利用这些模型中的原语去解决问题。学习那些让你在独立且自信的编写与实现任何规模并发系统时所需要用到的技巧和工具。 理解Go语言如何解决并发难以编写正确这一根本问题。 学习并发与并行的关键性区别。 深入到Go语言的内存同步原语。 利用这些模式中的原语编写可维护的并发代码。 将模式组合成为一系列的实践,使你能够编写大规模的分布式系统。 学习 goroutine 背后的复杂性,以及Go语言的运行时如何将所有东西连接在一起。 作者简介 · · · · · · Katherine Cox-Buday是一名计算机科学家,目前工作于 Simple online banking。她的业余爱好包括软件工程、创作、Go 语言(igo、baduk、weiquei) 以及音乐,这些都是她长期的追求,并且有着不同层面的贡献。 目录 · · · · · · 前言 1 第1章 并发概述 9 摩尔定律,Web Scale和我们所陷入的混乱 10 为什么并发很难? 12 竞争条件 13 原子性 15 内存访问同步 17 死锁、活锁和饥饿 20 确定并发安全 28 面对复杂性的简单性 31 第2章 对你的代码建模:通信顺序进程 33 并发与并行的区别 33 什么是CSP 37 如何帮助你 40 Go语言并发哲学 43 第3章 Go语言并发组件 47 goroutine 47 sync包 58 WaitGroup 58 互斥锁和读写锁 60 cond 64 once 69 池 71 channel 76 select 语句 92 GOMAXPROCS控制 97 小结 98 第4章 Go语言并发模式 99 约束 99 for-select循环103 防止goroutine泄漏 104 or-channel 109 错误处理112 pipeline 116 构建pipeline的最佳实践 120 一些便利的生成器 126 扇入,扇出 132 or-done-channel 137 tee-channel 139 桥接channel模式 140 队列排队143 context包 151 小结 168 第5章 大规模并发 169 异常传递169 超时和取消 178 心跳 184 复制请求197 速率限制199 治愈异常的goroutine 215 小结 222 第6章 goroutine和Go语言运行时 223 工作窃取223 窃取任务还是续体 231 向开发人员展示所有这些信息 240 尾声 240 附录A 241

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值