Go channelはソース分析を実現します.

13363 ワード

go通路はgoの合併スケジュールに基づいて実現します.自分は複雑ではありません.go同時スケジュールは私のこの文章を見てください.go同時スケジュール原理は勉強します.
1.hannelデータ構造
type hchan struct {
   qcount   uint               //           
   dataqsiz uint               //        
   buf      unsafe.Pointer     //      
   elemsize uint16             //    
   closed   uint32             //    ,0   ,1  
   elemtype *_type             //     
   sendx    uint               //    
   recvx    uint               //    
   recvq    waitq              //        
   sendq    waitq              //        
   lock mutex                  // 
}
type waitq struct {
   first *sudog
   last  *sudog
}
 
2.チャンネルを作成して実現する
チャンネルを作成する例:
ch:=make(chan int,4)
実現関数:
func make chan(t*change type,size int 64)*hchan
大まかな実現:
上の行のコードを実行するとnewのhchan構造ができます.同時にdataqsiz=4のintタイプの循環列を作成します.つまり、4つの要素が入っている行列です.順番に中にデータを書きます.書いたら0から書きます.この順序の索引はhchan.sendxです.
 
3.データの送信
送信データの例:
ch
送信データ実現関数:
func chans(c*hchan、ep unsafe.Pointer、block book、calerpc uintptr)book l
ステップは、データを送信する先頭アドレスを指します.
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
   lock(&c.lock)
   if c.closed != 0 {
      unlock(&c.lock)
      panic(plainError("send on closed channel"))
   }
 
   if sg := c.recvq.dequeue(); sg != nil {
      //                
      //           ,        ,       ,      
      //    ,                ,                  ,                         
      send(c, sg, ep, func() { unlock(&c.lock) }, 3)
      return true
   }
 
   if c.qcount < c.dataqsiz {
      //      ,               ,    ,
      qp := chanbuf(c, c.sendx)
      typedmemmove(c.elemtype, qp, ep)
      c.sendx++
      if c.sendx == c.dataqsiz {
         c.sendx = 0
      }
      c.qcount++
      unlock(&c.lock)
      return true
   }
 
   if !block {
      unlock(&c.lock)
      return false
   }
   //          ,ch //       
   //     ,      ,      ,         G ,         
   gp := getg()
   mysg := acquireSudog()
   c.sendq.enqueue(mysg)
   goparkunlock(&c.lock, "chan send", traceEvGoBlockSend, 3)
 
   // G   ,         ,         
   releaseSudog(mysg)
   return true
}
大まかな実現:
1:受信待ち行列は空ではなく、受信待ち行列から最初の受信者*susudogを取り出し、データをsudog.elemにコピーし、コピー関数はmemmove用のアセンブリで実現し、受信者データをあなたに通知しました.受信側の協働は待ち状態から実行可能状態に変更され、現在の協働手順を協働キューに参加し、スケジュールを待つことになります.
2:受取者がいなくて、バッファエリアがあり、満杯ではなく、直接にバッファにデータをコピーし、書き込みバッファエリアの位置はhchan.buf[sendx+]であり、バッファエリアがsendx=0に達したら、循環行列の実現であり、sendx指定の位置にデータを書き込み、hchan.qcount++
3:受信者がいない、バッファエリアがない、またはいっぱいであれば、現在のプロセスに対応するPのsudogキューからstruct sudogを取って、sudog.elemにデータをコピーして、sudogをsendqキューに参加して、受信者に通知します.現在のプロセスがブロックされ、呼び覚まされるのを待っています.受信者が通知を受けたら、引き続き実行します.受信データが完了したら送信者に通知します.送信側協働状態が待ち状態から運転可能状態に変更され、協働に参加して運転可能な列が実行されるのを待っています.
1:チャネルバッファが満杯になる前に、送りたいデータをバッファにコピーするだけで戻ってきます.
2:受信者がいる場合、受信者のデータ構造にコピーされたデータがある(最終的にデータを受信した変数ではなく、受信関数を実行したときに最終的にデータを受信した変数にコピーされる).受信協働を起動すると詰まります.もちろん、バッファがいっぱいで、受信者もいない場合は、データを送信キューにパッケージ化します.現在のプロセスは待機状態に設定されています.この状態はスケジュールされず、受信者がデータを受信したときに起動されます.
 
4.受信データ
受信データの例:
val:=
受信データ実現関数:
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
   lock(&c.lock)
   if sg := c.sendq.dequeue(); sg != nil {
      // Found a waiting sender. If buffer is size 0, receive value
      // directly from sender. Otherwise, receive from head of queue
      // and add sender's value to the tail of the queue (both map to
      // the same buffer slot because the queue is full).
      recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
      return true, true
   }
 
   if c.qcount > 0 {
      // Receive directly from queue
      qp := chanbuf(c, c.recvx)
      if ep != nil {
         typedmemmove(c.elemtype, ep, qp)
      }
      typedmemclr(c.elemtype, qp)
      c.recvx++
      if c.recvx == c.dataqsiz {
         c.recvx = 0
      }
      c.qcount--
      unlock(&c.lock)
      return true, true
   }
 
   if !block {
      unlock(&c.lock)
      return false, false
   }
   //       
 
   //      
   gp := getg()
   mysg := acquireSudog()
   c.recvq.enqueue(mysg)
   //   G        ,    
   goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3)
 
   //  G           
   mysg.c = nil
   releaseSudog(mysg)
   return true, !closed
}
大まかな実現:
1.送信キューが空ではない(バッファがいっぱいであることを説明します).送信キューから最初の送信者*sudogを取り出します.
1.1.バッファエリアがなく、送信待ちのデータをそのままsudog.elemにコピーし、受信データの変数valに保存し、送信者に処理が完了したことを通知します.引き続き実行してもいいです.
1.2バッファエリアがあり、バッファエリアhchan.buf[recvx]に対応する要素をvalにコピーし、送信者sudog.elemをhchan.buf[recvx]にコピーし、送信者は順番に書き、通常のFIFOは先進先に出ることを保証するために、先にコピーして、列の先頭要素を対応するバッファにコピーします.バッファがいっぱいになったら、キューを書きます.受信する時はバッファからデータを取って、外した後に空いている位置を送信キューから一番目に取って埋めて、対応するGを呼び覚ましてください.送信列が空いていない限り、バッファは必ず満たされます.
2.送信キューが空で、バッファエリアが空ではないので、バッファエリアのhchan.buf[recvx]に対応する要素をval,hchan.qcountにコピーします.
3.送信キューが空で、バッファエリアも空です.受信待ちのデータがないと、受信プロセスはsudogに包装され、受信待ち行列recvqに参加して、現在実行中のフローが詰まります.データを送信したら呼び覚まされます.
 
5.チャンネルFIFOは一回説明しています.
5.1:バッファがいっぱいではありません.データを送るとバッファに入ります.データを受け取るとバッファが出ます.分かりやすいです.
5.2:バッファがいっぱいになりました.送信データは待ち行列に入ります.受信データは先にバッファキューを出します.つまり受信したいデータのために、列が出るのを待って、データをバッファキューが列から出たばかりの位置に存在します.列が出たばかりの位置はバッファキューの末尾に相当します.つまり待ち行列の先頭がバッファキューの末尾につながっています.待ち行列の列の先頭をキャッシュ列の列の最後に参加して、バッファキューがいっぱいであることを保証して、減らすのはバッファキューの中のデータで、先進的な先に出ることを保証します.
5.3:データの受信、バッファまたは待ち行列にデータがあります.最初のものを持っていくと、待ち行列がバッファの最後に接続されていることを保証します.つまり、バッファの最後に空きがあると、待ち行列が列から出てきて、バッファの最後に塗り込めます.そうでないと、自分で梱包して受信キューに参加します.前Gが待機状態に入ると、データの送信があれば自然に通知します.
 
まとめ:Go channel goの同時スケジュールに基づいて、渋滞と非閉塞の二つの通信方式を実現する.
 

<div id=「right-1」class=「col-lg-12 col-sm-4 col-xs-4 ad」


<div id=「right-2」class=「col-lg-12 col-sm-4 col-xs-4 ad」


<div id=「right-3」class=「col-lg-12 col-sm-4 col-xs-4 ad」