ゴルーチン和チャンネル

最初の並行性の伝統的なモデルは、マルチスレッド共有メモリであり、第二は、現代の同時実行モデル、通信シーケンシャルプロセス(CSP)は、並行プログラム内の言語は、2つの手段によって達成することができる移動し、移動し、言語ゴルーチン通信プロセスをサポートするチャネルの順番。


、ゴルーチン
同時実行ユニットのそれぞれがゴルーチンと呼ばれ、言語に1.を。
2.メインゴルーチン:プログラムが開始されると、すなわちメインゴルーチンと呼ばれる別のゴルーチンで実行されている主な機能、。
3.ゴルーチンを作成:作成するための新しいゴルーチン使用行く文を。文は、共通の関数やメソッドの呼び出しで前に構文的には、キーワード、外出先で行きます。それはゴルーチン新しく作成された文の実行で機能し、それ自体が迅速に完了される文を行くだろうという声明を行きます。

主な機能は、返すように4、すべてのゴルーチンは、プログラムが終了直接中断されます。


二つは、チャンネル
ゴルーチンは、その後、ボディランゲージ囲碁プログラムによって複雑にされている場合、チャネルは、それらの間の通信メカニズムです。
チャネルを介して、ゴルーチン値情報は、他のゴルーチンに送られてもよいです。
送信されることができる値の各タイプは、唯一のチャネルです。
1.チャンネルを作成

チャネルは、参照型の変数です。
2.チャネルコンペア
同じチャネル型の二つが==演算子を使用して比較することができます。2つのチャンネルが同じオブジェクトを参照する場合、比較の結果が真です。チャネルはまた、比較及びゼロことができます。
データを送受信するための3チャネル

操作の結果はまた、合法的受信する受信機を使用しないでください。
4.チャンネルを閉じます

任意の送信操作に基づいてチャネルがパニック例外が発生します閉じられています。
チャネルに基づいてチャネルがデータ、後続の受信動作しなくなり、ブロックを持っていない場合は、依然として正常に受信することができる送信されたデータの受信動作を実行する前に閉じ、代わりにすぐにゼロの値を返してきました。
5.チャネルバッファなしの
伝送チャンネルにベースバッファなし操作は、別のゴルーチン受信動作を順にチャネルを介して送信成功後の送信の値は2つのゴルーチンを続けるために、同じチャネル上で実行されるまでブロックされた送信者ゴルーチンの原因となります声明の履行の背後にあります。受信動作が最初に発生した場合、逆に、受信機は、同じ伝送チャネル上で行わ別のゴルーチン動作までブロックゴルーチンう。
バッファなしチャネル送信をベースとし、受信操作はこのような理由のために、同期化を行う2つのゴルーチンになり、何のチャンネルが時には同期チャネルと呼ばれていないキャッシュ。
注:
一部のイベントメッセージは、追加情報を搬送しない、彼らは単に構造体{}空気型構造がチャネルの要素として使用されるときに使用可能な2つのゴルーチン間の同期として使用されています。
6チャネルのシリーズ(パイプライン)
チャネルは、入力としてチャネルの次のチャネルの出力、複数のゴルーチンを一緒にリンクするために使用されてもよいです。チャンネルのこのシリーズは、パイプライン(パイプライン)と呼ばれています。
チャンネル7の単方向
<ちゃん- int型:int型チャネルのみを送信すると、送信を受信することはできませんが
、<ちゃんint型:int型チャネルのみを受信、送信のみ受け取ることができません
。この制限は、コンパイル時に検出されます。
注意:
唯一のアサーションのためのクローズ操作は、もはやチャンネルがコンパイルエラーになるだけ近いの呼び出しを受けたので、近い関数を呼び出しますので、送信者だけがゴルーチンに位置しているチャンネルに新しいデータを送信しないので。
任意の双方向一方向へのチャネルのチャネル割当変数は暗黙的な変換をもたらす、一方向チャネルのための双方向チャネルの変換。構文の逆変換が存在しません。
チャネル8.非バッファ
キューバッファの保持要素を有する内部チャネル。

内部キャッシュ容量チャネル(CH)CAP
チャネル内部キャッシュ長:LEN(CH)
緩衝チャネルに基づいて送信バッファキュー操作の尾部は、受信動作は、キューの先頭から要素を削除するためにされた要素内に挿入されます。内部バッファキューがいっぱいの場合、他のゴルーチン受信動作が行われ、新しいキュースペースの解放されるまで、それがブロックされます送信します。チャネルが空の場合は逆に、操作がキューに要素を挿入する操作を行うゴルーチン別の送信を受信するまでブロックされます。
送信チャネルの受信バッファキューとゴルーチンを切り離します。


三、并发的循环
1. goroutine泄露
某个channel的内部缓存队列满了,但是没有goroutine向这个channel执行接收操作,从而导致向这个channel发送值的所有goroutine都永远阻塞下去,并且永远都不会退出。这种情况,称为goroutine泄露,这将是一个bug。和垃圾变量不同,泄露的goroutine并不会被自动回收,因此确保每个不再需要的goroutine都能正常退出是很重要的。
2. sync.WaitGroup
有时候,我们需要多个goroutine都运行结束以后做一些事情,比如关闭一个channel。Go语言提供了一种特殊的计数器,用于检测多个goroutine是否都已运行结束。它在每一个goroutine启动时自增1,在每一个goroutine退出时自减1,且会一直等待直至计数器自减为0,即表示所有goroutine都已运行结束。
使用示例:

func makeThumbnails(filenames <-chan string) int64 {
	sizes := make(chan int64)
	var	wg sync.WaitGroup//number of working goroutines
	for	f := range filenames {
		wg.Add(1)//必需在worker goroutines开始之前调用,才能保证Add()是在closer goroutine调用Wait()之前被调用
		//worker goroutines
		go func(f string) {
			defer wg.Done()//使用defer来确保计数器即使是在程序出错的情况下依然能够正确地自减
			thumb, err := thumbnail.ImageFile(f)
			if err != nil {
				log.Println(err)
				return
			}
			info, _ := os.Stat(thumb)
			sizes <- info.Size()
		}(f)
	}
	//closer goroutine
	go func() {
		wg.Wait()
		close(sizes)
	}()
	var	total int64
	for	size := range sizes {
		total += size
	}
	return total
}

 

四、基于select的多路复用
有时候我们需要等待多个channel的其中一个返回事件,但是我们又无法做到从每一个channel中接收信息。假如我们试图从其中一个channel中接收信息,而这个channel又没有返回信息,那么程序会立刻被阻塞,从而无法收到其他channel返回的信息。这时,基于select的多路复用就派上用场了。

select会一直等待直至有能够执行的case分支才去执行这个case分支。当条件满足时,select才会去通信并执行case之后的语句;这时候其它通信是不会执行的。一个没有任何case的select语句写作select{},会永远地等待下去。
如果多个case同时就绪时,select会随机地选择一个执行,这样来保证每一个channel都有平等的被select的机会。
channel的零值是nil,对一个nil的channel发送和接收操作会永远阻塞,在select语句中操作nil的channel永远都不会被select到。

 

五、消息广播
有时候,我们需要将某个消息广播通知所有运行中的goroutine,但是我们又无法知道goroutine的数量,这时候可以使用这种策略:创建一个空的channel,将从这个channel中接收信息的操作加入基于select的多路复用,由于这时channel是空的,所有的goroutine都无法从中接收到值;当我们需要广播消息的时候,关闭这个channel,这样所有的goroutine都能从中接收到零值,以此表示该消息已传达。(一个channel只能用于表达一种消息)

 

六、一个综合运用的实例程序——并发的字典遍历

/**
*广度遍历目录文件,计算文件数量与大小
*每隔0.5秒输出已计算的文件数量与大小
*当标准输入有值时,中断程序运行
**/
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

//广度遍历目录文件,计算文件数量与大小
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
	defer n.Done()
	if cancelled() {
		return
	}
	for _, entry := range dirents(dir) {
		if entry.IsDir() {
			n.Add(1)
			subdir := filepath.Join(dir, entry.Name())
			go walkDir(subdir, n, fileSizes)
		} else {
			fileSizes <- entry.Size()
		}
	}
}

/*
*目的:控制同一时刻打开的文件数量,防止程序占用太多资源
*实现:创建一个带缓存的channel,通过channel的缓存队列大小控制同一时刻打开的文件数量。
*读取文件之前向该channel发送一个信号量,读取完以后向该channel接收一个信号量,若是channel的缓存
*队列满了,则会阻塞,无法继续打开文件。
 */
var sema = make(chan struct{}, 2)

func dirents(dir string) []os.FileInfo {
	select {
	case sema <- struct{}{}:
	case <-done:
		return nil
	}
	defer func() { <-sema }()
	entries, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprint(os.Stderr, "du1: %\n", err)
		return nil
	}
	return entries
}

/*
*目的:读取标准输入,只要有值则停止运行程序
*实现:创建一个空channel,每个goroutine读取文件之前都先从channel中接收值,如果阻塞,表示程序可以继续运行,
*当标准输入有值时,关闭该channel,所有未读取文件的goroutine从channel中接收到零值,中断程序运行。
 */
var done = make(chan struct{})

func cancelled() bool {
	select {
	case <-done:
		return true
	default:
		return false
	}
}

func main() {
	//读取标准输入,只要有输入则停止程序
	go func() {
		os.Stdin.Read(make([]byte, 1))
		close(done)
	}()

	flag.Parse()
	roots := flag.Args()
	if len(roots) == 0 {
		roots = []string{"."}
	}
	//用于发送文件大小的channel
	fileSizes := make(chan int64)

	/*
	*目的:当读取文件大小的所有goroutine都结束运行时,关闭用于发送文件大小的channel
	*实现:使用特殊计数器sync.WaitGroup,每次新增一个goroutine则自增1,每次结束一个goroutine
	*则自减1,当其自减为0时,表示所有goroutine都已结束运行,可以关闭channel了
	 */
	var n sync.WaitGroup
	for _, root := range roots {
		n.Add(1)
		go walkDir(root, &n, fileSizes)
	}
	go func() {
		n.Wait()
		close(fileSizes)
	}()

	/*
	*目的:每隔0.1秒输出文件数量与大小计算的结果
	*实现:使用time.Tick函数。time.Tick函数返回一个channel,程序会周期性地像一个节拍器一样向这个channel发送事件,
	*每一个事件的值是一个时间戳。当从该channel中接收到值的时候,就可以打印了。
	 */
	var tick <-chan time.Time
	tick = time.Tick(100 * time.Millisecond)

	var nfiles, nbytes int64
loop:
	for {
		select {
		case <-done:
			/*
			*程序结束,需要先吧fileSizes这个channel中的内容排空,以保证对walkDir的调用不会被向fileSizes发送信息阻塞住,
			*可以正确地完成,防止goroutine泄露。
			 */
			for range fileSizes {
			}
		case size, ok := <-fileSizes:
			if !ok { //fileSizes这个channel已关闭,退出循环loop
				break loop
			}
			nfiles++
			nbytes += size
		case <-tick:
			printDiskUsage(nfiles, nbytes)
		}
	}
}

//打印已经计算的文件数量与大小
func printDiskUsage(nfiles, nbytes int64) {
	fmt.Printf("%d files  %.1f GB\n", nfiles, float64(nbytes)/1e9)
}

 

おすすめ

転載: www.cnblogs.com/wujuntian/p/11241171.html