ゴーコア開発の研究ノート(Nianba) - コルーチンゴルーチン&パイプラインチャンネル

問題:1から20000の間のすべての素数の検索
従来の方法を: 2を行うには、各番号は<= I <= N - 1 それは2000000000であれば時間は、コンピュータが泣いて、サイクルを法。
Golang方法:並列に同時増加ではなく、伝統的なサイクルは、とても効率がはるかに高くなり、また分散のニーズを満たすことならば物事は必ずプロセスは、まだプロセスです。

ソリューション
同時または並列的に使用してタスクを複数の素数の統計に割り当てられたのでことは、コルーチンゴルーチンプログラムの導入に、この時間を完了するために、ゴルーチン。
4つの等しい部分に分布に応じて、1から20000仮定に基づいて計算し、CPUのマルチコア・スタイル、分散クラスタに沿って、大幅に高速です。

プロセスとスレッド

  1. 学んだLinuxの人は大体、いくつかのノートを作り、これを言っているのではない、手順、プロセス、スレッド間の全体の関係を知っている必要があります
  2. プログラム命令、データおよび組織形態の説明。
  3. プロセスは、オペレーティングシステムでプログラムの実行の過程で、システムは、リソース割り当ておよびスケジューリングの最小単位ように、互いに独立してそれぞれ他の名前空間とは異なるプロセスで、。
  4. スレッド実行インスタンスをプロセスとして理解することができる、実行することができるオペレーティング・システムの最小単位であり、スレッドID、現在の命令ポインタによって標準スレッドを設定し、スタックコンポーネントを登録します。
  5. プロセスは****「コンテナ」のスレッドで、スレッドを直接プロセスの崩壊の崩壊に至る過程に影響を与えるような嵐のビデオ、ビデオとオーディオの同期(同時)としては、スレッドコラボレーションとして理解することができます。

並行並列

  1. シングルコア上で実行されているマルチスレッドプログラムは、複雑さ(時分割多重)CPUタスク間のスイッチミリ秒のオーダーで、同時に真の実行同時ウェブ、ウェブを区別するために、複数のタスクで一緒に実行されているように見えます。
  2. マルチコア上で実行されているマルチスレッドプログラムは、平行であり、真のマルチタスク効率の点で、同時には間違いなくより高い同時並行効率よりなります。

コルーチンゴルーチン

  1. メイン処理は、各プロセスは、少なくとも一つのメインスレッドを有し、直接CPU、リソースのヘビー消費量に、物理的プロセスです。
  2. 軽量スレッドからコルーチンのメインChengkaiチー、ロジック状態、リソース消費の小さな。
  3. Golangコルーチンメカニズムは簡単コルーチンのオープン数千人に、重要な特徴です。
  4. メインスレッドが(多少違うと言って、実際には、プロセスとして理解することができる)、スレッド上でより多くのコルーチン行くを再生することができます行く、スレッドは軽量コルーチン(根本的な最適化を行うためのコンパイラ)です。
  5. コルーチンの特徴行く
    独立したスタック領域を
    ヒープスペース共有している
    ユーザースペース・レベルでスケジューリングを
    コルーチン軽量スレッドです

ゴルーチン使用

  1. 一般的な機能が追加、唯一の実装の終了前にメインスレッドで使用するためのものです)(ゴー<関数名>を完了させます。
  2. メインにかかわらず、タスクコルーチンの実行されていないスレッドが終了するが、最終的にシャットダウンするように強制されます(メインスレッドのスタック空間が閉鎖され、彼の家にノーコルーチン)。

ケースプレゼンテーション:

package main

import (
	"fmt"
	"time"
)

func echo() {
	for i := 0 ; i <= 10 ; i++ {
		fmt.Println("echo() 我是练习时长两年半的xx")
		time.Sleep(500 * time.Millisecond)
	}
}

func main() {
	/*
	一般都是根据函数走的,只需要在前面添加 go <函数名>()
	 */
	go echo()      //开启协程
	
	for i := 0 ; i <= 10 ; i++ {
		fmt.Println("main() 喜欢唱,跳,rap,篮球")
		time.Sleep(time.Second)
	}
	//go echo()    //这种开启协程方式无效,因为主函数已经结束了,充分理解原理

}   //输出效果可以echo()和main()中是同时执行的(for循环表现为穿插)

MPGのゴルーチンのスケジューリングモデル

M:直接KSM関連カーネルスレッド< - > Mは、Mの複数Mの複数の複数のCPUで並列に実行され、単一のCPU上で同時に実行されます。
P:コンテキスト間の通信は、ブリッジのGとM、P本ランキュー、キュー複数G.
G:コルーチンゴルーチン、軽量のスレッドであり、状態は簡単に行くと何千ものコルーチンの数十から、論理的です。

マルチスレッドのJavaおよびCと比較すると、一般的に、カーネルモードは、各スレッドは、スレッドの数千人は、CPUが不足することがあり、8M程度の比較的重量級であり、
かつゴルーチンリソースの消費量はおそらく、非常に低いです* 2kの各コルーチン

MPG:モデルを実行

  1. G Pは、Gの処理キューポップ的に実行キュー、P一つずつあります
  2. Gキュー大きなファイルがM0は、追加Mを作成コルーチン原因閉塞を、読み取られた場合、(依存オペレーティングシステム、スレッドプール)M1であると仮定され、Pは、ポップ請求M1を装着し続けますG; M0はG0を遮断され続けます。
  3. MPGスケジューリングモードのみコルーチン実行は、他のコルーチンのキューがブロックされていますせません、あなたはまだ、並行/並列することができますことができます。

アクティブなCPU Golangの数を設定します。

package main

import (
"fmt"
"runtime"
)

func main() {
	num := runtime.NumCPU()      //显示当前系统逻辑CPU个数
	fmt.Println(num)
	fmt.Println(runtime.GOMAXPROCS(num - 1))  //设置可同时执行的CPU最大个数
	// 这个在Golang1.8之后不用在设置了
}

いくつかの問題が発生してGolangコルーチン同時/並列に対応

  1. 並行処理/並列性を解決する方法の書き込み動作の間、問題。
  2. 避けるためにどのようにメインプログラムを実行を完了するための出口を実行する状況コルーチンまで待てません
  3. どのように異なる通信ゴルーチン間の通信:
    グローバルmutex変数
    パイプチャネルを使用して、

グローバルミューテックス示す:1-10をコルーチン方法を持つすべての要因の処理結果を、以下のように、出力結果は、1要因要因効果の結果は、1,2 ... 2であります

package main

import (
	"fmt"
	"sync"
)
var FactMap = make(map[int]int,10)
var lock sync.Mutex   //变量是一个结构体变量,有两个方法 lock() unlock()

func Factorial(n int) {
	res := 1
	for i := 1 ; i <= n ; i++ {
		res *= i
	}
	lock.Lock()
	FactMap[n] = res   //防撞写,使用lock
	lock.Unlock()
}

func main() {
	/*
	用协程方式处理1-200所有阶乘的结果,输出结果如下: 1的阶乘结果为1,2的阶乘效果为2...
	把上述结果加入一个map中
	思路分析:
	类似排队上厕所,如果坑里有人,那么就直接加锁,外面的人找个队列候着
	 */
	for i := 1; i <= 10 ; i++ {
		go Factorial(i)
	}
	fmt.Println(FactMap)
}

なぜパイプチャネルを使用

  1. 通信の問題を解決するために、グローバル変数を使用してロック同期は完璧ではないゴルーチン。
  2. メインプログラムは完了時間を定義するために一つのキーワードとして延期に似た時間内に完了したすべてのゴルーチン、待ちに決定することは困難です。
  3. メインスレッドの使用の睡眠時間制御はそれほど不適切で、飛ぶことはありません。
  4. 時々あまり、あまりにも複雑で、解析すべき位置をロックプラス、プラス様々な問題が間違って表示されます。
  5. ★★★コードの品質の本質は:共有メモリを介して通信していない、とメモリは、通信で共有します!

導入管チャンネル

  1. それは本質的に、データ構造-キュー、FIFO:FIFO、スタック:最後のアウト。
  2. アレイ、その後のスライスは、パイプラインの実装に加えて、リンクリストがキュー、パイプラインを実装することができます。
  3. パイプは、容量の上限であるすべてのキューの内容は、空気ダクトに出された後、。
  4. スレッドセーフでは、問題のヒットを解決するために、マルチゴルーチン訪問、書き込みをロックする必要はありません
  5. チャネル型自体は、一般的に、ミックスをお勧めしませんミックス場合、タイプ必要なアサーションの値

ステートメント導管チャネル:
VAR <導管変数名>チャン<データタイプ>
VAR intChanチャンint型
VAR stringChanチャンストリング
VAR infChanチャンインターフェース{}
VAR mapChanチャンマップ[INT]ストリング

パイプストリングstringChan =メイク(ちゃん文字列、10)//文字列の10

  1. チャネルは、参照型です。
  2. チャンネルはを利用するために初期化する必要があります
  3. パイプが空のインターフェイスモードを置くミックスとして定義されている場合、パイプの種類があります。
  4. パイプが空のインタフェースとして定義されている場合、アサーションのタイプを使用することを確認したときに変数値、データのデフォルトのパスは、インターフェイスタイプを考慮されるからです。
  5. )(クローズを使用することで、パイプラインをオフにすることができますのみ読み取ることができますが、入口の閉鎖に相当する、閉じた後に書き込めません
  6. 定義されたチャネルは、読み取り専用または書き込み専用、個々の送信に対応してもよい
    VAR intChan1チャン< - // INT書き込み専用パイプ
    VAR intChan2 <整数//読み取り専用パイプちゃん

パイプのケースを理解することによってパイプラインのチャネル使用例

package main
import "fmt"

func main() {
	var stringChan chan string
	fmt.Println(stringChan)        //nil  符合引用类型不开辟内存栈就是nil
	stringChan = make(chan string,3)  //管道容量为3
	fmt.Println(stringChan)        //0xc000038060 发现是一个地址
	fmt.Println(&stringChan)       //0xc000082018 这个是管道变量的地址,也就等于说管道就是个指针变量,所以支持多个协程操作管道

	//向管道写数据
	stringChan<- "蔡徐坤"          // \管道变量名称\<- string 
	str := "鸡你太美"
	stringChan<- str              // 

	//查看管道容量len() cap()
	fmt.Printf("%v %v", len(stringChan),cap(stringChan))  //len是值目前管道中使用容量 2 ,cap是指管道总容量 3 
	//注意管道一定不能超,超了容量所有的goroutine都会deadlock,管道的价值在于边放边取
	//在不使用goroutine的情况下,管道中的数据取完了,也会报deadlock,注意这些。
	
	//从管道取出1个元素,管道的len()会减1
	var _ string
	_ = <-stringChan   //取出一个元素
	fmt.Printf("%v %v", len(stringChan),cap(stringChan))   //len是值目前管道中使用容量 1 ,cap是指管道总容量 3

	//还剩1个元素,鬼畜一般再取2次
	<-stringChan           //还剩0个,直接扔 <-stringChan
	//_ = <-stringChan         //deadlock
	fmt.Printf("%v %v", len(stringChan),cap(stringChan))

	var infChan chan interface{}
	infChan = make(chan interface{},3)
	infChan <-"唱,跳,rap,篮球"
	infChan<- 666
	rev := <-infChan      //唱,跳,rap,篮球
	fmt.Println(rev) 
	close(infChan)        //关闭信道,这样里面还剩下 666 int,不可以再接收了,只能再把666读出来了。
	fmt.Println(len(infChan))  //1
	infChan<- "shit!"   // panic: send on closed channel
}

パイプラインについてトラバースをシャットダウン

  1. 管が閉じていない場合、デッドロックエラーが発生するのは、トラバースLEN導管1に低減されるので、そのトラバーサル相当するN / 2 +1はデータを直接、デッドロックが、すべてのデータがトラバースされていない場合
  2. パイプを通過した場合、パイプは通常、データを通じて、閉じていなければなりません、それは完全な終了を横断します
    package main
    
    import "fmt"
    
    func main() {
    	var intChan chan int
    	intChan = make(chan int , 100)
    
    	for i := 1 ; i <= 100 ; i++ {
    		intChan<- i
    	}
    	//没有加close(intChan)就会报错
    	close(intChan)
    	for v := range intChan {
    		fmt.Println(v)
    	}
    }
    

前コルーチン知識及びパイプラインによると、統合された場合の処理

1.コルーチンを待たずに、直接、プログラムの終了前に完了するためにメインスレッドを解決するためにパイプラインを完了することを学びます。

package main

import "fmt"

func writeData(intChan chan int) {
	for i:=1;i<=50;i++ {
		intChan<- i
	}
	close(intChan)    //后续就可以用for循环读了
}

func readData(intChan chan int,exitChan chan bool) {
	for {
		v, ok := <-intChan   //如果管道中没有数据了,ok默认为true,会变为false
		if !ok {             //
			break
		}

		fmt.Printf("读到数据为:%v",v)
	}
	exitChan<- true
	close(exitChan)
}

func main() {
	/*完成协同工作案例
	1. writeData协程,向管道写如50个int
	2. readData协程,负责从管道中读取writeData写入的数据
	3. 上述两个协程操作同一个管道
	4. 主线程要等待上述两个协程都完成后,才能结束
	 */
	intChan := make(chan int,50)
	exitChan := make(chan bool,1)
	go writeData(intChan)
	go readData(intChan,exitChan)
	
	//接下来保证协程结束后,才结束主线程
	for {
		<-exitChan
		//_, ok := <-exitChan    //反复读取,直到读空
		//if !ok {
		//	break
		//}
	}
}

2.使用コルーチンパイプと1と100の間の範囲のすべての素数の完了

package main

import "fmt"

func inNum(intChan chan int) {
	for i := 3; i <= 100 ; i++ {
		intChan<- i
	}
	defer close(intChan)
}

func outChan(intChan chan int,primeChan chan int,exitChan chan bool) {
	for {
		num, ok := <-intChan
		if !ok{         //管道里面取不到东西了,凡是赋值变量取所有管道内数据都是用这种方法。
			break
		}
		//判断num是不是素数
		for i := 2; i <= num ;i++ {
			if num % i == 0 && i < num {
				//不是素数不要
				break
			}
			if num == i {
				primeChan<- i
			}
		}
	}
	exitChan<- true

}

func main() {
	intChan := make(chan int,2000)
	primeChan := make(chan int,2000)
	exitChan := make(chan bool,4)

	start := time.Now().Unix()
	//协程 放入数据
	go inNum(intChan)    //defer 直接后续关闭intChan管道

	//开启四个协程,从intChan里面取出数据,并判断是否为素数,是就放入primeChan中
	for i := 0 ; i <= 3 ; i++ {
		go outChan(intChan,primeChan,exitChan)
	}

	for i := 0; i < 4 ; i++ {
		<-exitChan             //当收到管道中有四个true时则说明协程已经取完了primeChan中所有的内容
	}
	close(primeChan)           //所以可以关闭管道

	end := time.Now().Unix()
	fmt.Println("使用协程耗时为:",end - start)   //查看协程节省多少时间,结果确实证实可以提高效率
	
	for {
		res, ok := <-primeChan    //遍历primeChan中所有的素数
		if !ok {
			break
		}
		fmt.Println("素数为:",res)
	}
}

パイプが閉じているとき3.知らない、あなたは問題を解決する選択ダクト閉塞を使用することができます。

package main

import "fmt"

func main() {
	/*
	select语句,依次向下执行,解决阻塞问题,也不用考虑何时需要close(varchan)了
	 */
	var intChan = make(chan int, 10)
	var strChan = make(chan string,10)
	for i := 0 ; i < 5 ; i++ {
		intChan<- i
	}
	for i := 0 ; i < 5 ; i++ {
		strChan<- "shit" + fmt.Sprintf("%d",i)
	}

	//close(intChan)           //其中传统管道关闭方法
	//for v:= range intChan {
	//	fmt.Println(v)
	//}
	
	for {
		select {
		case v := <-intChan:
			fmt.Println(v)
		case s := <-strChan:
			fmt.Println(s)
		default:
			fmt.Println("不玩了继续下面了")
			return   //跳出整个for循环而不是select,也可以使用label...break方式来结束for循环从而终止main()		
		}
	}
}

4.機能がある場合はパニックが補助機能をパニック回避する方法、ありました主な機能は延期+回復を継続して使用することはできませんが発生します。

package main

import (
	"fmt"
	"time"
)
var strChan = make(chan string,100)

func test1() {
	for i := 0 ; i < 10 ; i++ {
		strChan<- "鸡你太美" + fmt.Sprintf("%d",i)
	}
	close(strChan)
	for {
		v, ok:= <-strChan
		if !ok {
			break
		}
		fmt.Println(v)
	}

}

func test2() {
	/*匿名函数捕获错误,不要影响其他协程和主线程运行*/
	defer func() {
		if err := recover();err != nil {
			fmt.Println("test2()协程发生错误是:",err)
		}
	}()

	var shitMap map[int]string   //这里没有为shitMap分配一个栈,肯定会报错
	shitMap[1] = "蔡徐坤"        //而这个协程阻止了主线程和另外一个协程的运行如果避免则需要加入defer+recover捕获错误
}

func main() {
	go test1()
	go test2()

	time.Sleep(10 * time.Second)
}

実行結果:test1のは、コンテンツ、TEST2エラーを出力し、メインプログラムはオフ、10秒を待ちます。

公開された49元の記事 ウォン称賛18 ビュー3997

おすすめ

転載: blog.csdn.net/weixin_41047549/article/details/90548470