10ゴルーチン和チャンネル

並行プログラミングの概念に行く

ほとんどすべての言語が並行プログラミングをサポートしています。Goも例外ではなく、軽量の並行プログラミング(コルーチン)はGolangの重要な機能です。
基本コンセプト

プロセス:プロセスは、オペレーティングシステムでのプログラムの実行プロセスです。これは、リソースの割り当てとスケジューリングのためのシステムの基本単位です。
スレッド:プロセスの実行インスタンスであり、プログラムの最小の実行単位であり、プロセスよりも小さく、独立して実行できる基本単位です。プロセスは複数のスレッドを作成および破棄できます。プログラムには少なくとも1つのプロセスがあり、プロセスには少なくとも1つのスレッドが含まれます。
同時実行性:マルチスレッドプログラムは単一のコアで実行されます。これは同時実行性です。一度に実行されるスレッドは1つだけです。 、および複数のスレッドが交代で実行されます。実行(各スレッドが順番に実行される時間は非常に短く、巨視的には複数のスレッドが同時に実行されるように見えます)。
並列:マルチスレッドプログラムも複数のコアで並列に実行されており、特定の時間に複数のスレッドが実行されています。

Goスレッドとコルーチン:1つのGOスレッドで複数のコルーチン(Goに固有の軽量スレッド)を起動できます。
コルーチンの機能:独立したスタックスペース、共有プログラムヒープスペース、ユーザーが制御するスケジューリング、
メインスレッドは物理スレッドです。 CPUに作用し、重量があり、CPUリソースを非常に消費します。コルーチンは、メインスレッドから開始される軽量スレッドです。一般的な言語は、より多くのコルーチンスレッドを開くことをサポートしておらず、リソースの消費量は膨大になりますが、golangコルーチンは簡単に数万を開くことができます。
Golangは、プログラムが実行するCPUの数を設定できます(1.8以降のデフォルトではマルチコア操作)

num := runtime.NumCPU()
	runtime.GOMAXPROCS(num)
	fmt.Println("程序运行cpu数量:",num)

ゴルーチン

サンプルコード
コルーチンを開始して1秒あたり1文を出力し、メインスレッドも1秒あたり1文を出力します

func test01(){
    
    
	go sayHello()//启动协程执行逻辑

	for i:=0;i<10;i++{
    
    
		fmt.Println("主线程 ====》",i)
		time.Sleep(time.Second)
	}
}

//定义一个协程方法
//每秒输出一个 hello world 输出 10次
func sayHello(){
    
    
	for i:=0;i<10;i++{
    
    
		fmt.Println("协程 hello world!",i)
		time.Sleep(time.Second)
	}
}

実行結果、2つのロジックが同時に実行されていることがわかります

主线程 ====0
协程 hello world! 0
协程 hello world! 1
主线程 ====1
主线程 ====2
协程 hello world! 2
协程 hello world! 3
主线程 ====3
协程 hello world! 4
主线程 ====4
主线程 ====5
协程 hello world! 5
协程 hello world! 6
主线程 ====6
主线程 ====7
协程 hello world! 7
协程 hello world! 8
主线程 ====8
主线程 ====9
协程 hello world! 9

1〜100の整数の次数加算を計算し、結果をマップに出力します

//示例 多协程计算 0-200 每个数字的阶乘,把结果放到map 中
// 执行完成后主线程打印结果
//声明全局变量
//不加锁时 没有出现任何异常
var(
	resMap = make(map[int]int,10)
)
func test02(){
    
    
	for i:=1;i<=100;i++{
    
    
		go nmuls(i)
	}
	time.Sleep(time.Second*10)
	fmt.Println(resMap)//打印map
	//map[85:3656 48:1177 78:3082 35:631 41:862 55:1541 63:2017 66:221...
}
//阶加计算方法
func nmuls(num int)int{
    
    
	res:=1
	for i:=1;i<=num;i++{
    
    
		res += i
	}
	resMap[num]=res //将结果放入全局map中
	return res	
}

チャネル

異なるコルーチン間で通信する方法
は、グローバルミューテックスを使用するか、パイプを使用できます(パイプ自体はスレッドセーフです)

次のような
var変数名chanデータ型を宣言します。varintChanchanintintデータのみを配置するチャネルを宣言します。

注意点

  1. チャネルの本質はデータ構造キューであり、先入れ先出しです。
  2. チャネル自体はスレッドセーフです
  3. チャネルは型指定されており、宣言された型と同じ型のデータのみを格納できます。
  4. チャネルは参照型であり、データを書き込む前に初期化(make)する必要があります
  5. チャネル内の要素がいっぱいになった後、要素が配置されたときにデッドロック例外が報告されます
  6. コルーチンを使用せずに、チャネル内のデータがフェッチされている場合、フェッチ後にデッドロックが報告されます
  7. トラバーサル中にチャネルが閉じられていない場合、デッドロック例外が報告されます。理由は6と同じである必要があります。
  8. チャネルは、読み取り専用/書き込み専用として宣言できます。詳細については例を参照してください

サンプルコード

func test03(){
    
    
	var intChan chan int
	intChan = make(chan int,3)  //创建一个可以存放 3个int的channel
	fmt.Printf("intChan 值 =%v 地址是=%p \n",intChan,&intChan)
	//intChan 值 =0xc042076080 地址是=0xc04206e018

	//插入数据
	intChan<- 10
	intChan<- 2
	intChan<- 13
	//fatal error: all goroutines are asleep - deadlock! 
	//添加的元素超过 容量时 鲍deadlock 错误
	//intChan<- 4
	//intChan 长度 len= 3 容量= 3
	fmt.Printf("intChan 长度 len= %v 容量= %v \n",len(intChan),cap(intChan))

	//读取数据
	num2:=<-intChan
	fmt.Println("num2=",num2)
	fmt.Printf("intChan 长度 len= %v 容量= %v \n",len(intChan),cap(intChan))
	num3:=<-intChan
	num4:=<-intChan
	//没有元素的时候获取元素 会报异常 fatal error: all goroutines are asleep - deadlock!
	//num4=<-intChan
	fmt.Println(num3,num4)


	//创建可以放所有类型的 channel
	var aChan chan interface{
    
    }
	aChan = make(chan interface{
    
    },5)
	aChan<- 1
	aChan<- "123"
	aChan<- 12.12
	 o:=<-aChan
	fmt.Println("取出元素:",o)//取出元素: 1


	//channel 遍历 不能通过普通的for 循环遍历 (数量可能会变化)
	//
	// for i:=0;i<len(aChan);i++{
    
    

	// }
	//遍历时如果 channel 没有关闭 则会报 deadlock 异常,fatal error: all goroutines are asleep - deadlock!
	//如果channel 关闭了,则不会报异常,遍历完 程序退出
	fmt.Printf("aChan 长度 len= %v 容量= %v \n",len(aChan),cap(aChan))

	close(aChan)
	for v:=range aChan{
    
    
		fmt.Println("v=",v)
	}
}

データ同期の例を実現するためのミューテックスロック

//示例 多协程计算 0-200 每个数字的阶乘,把结果放到map 中
// 执行完成后主线程打印结果
//声明全局变量
//不加锁时 没有出现任何异常
var(
	resMap = make(map[int]int,10)
	lock sync.Mutex //定义全局排他锁
)
func test02(){
    
    
	for i:=1;i<=20;i++{
    
    
		go nmuls(i)
	}
	time.Sleep(time.Second*10)
	fmt.Println(resMap)//打印map
	//map[85:3656 48:1177 78:3082 35:631 41:862 55:1541 63:2017 66:221
}
//阶加计算方法
func nmuls(num int)int{
    
    
	res:=1
	for i:=1;i<=num;i++{
    
    
		res += i
	}
	lock.Lock()//写数据前枷锁
	resMap[num]=res //将结果放入全局map中
	lock.Unlock()//写数据完成进行解锁
	return res	
}

チャネルの例
素数である1〜20000の数を数えます

func test04(){
    
    
	intChan := make(chan int,1000)
	primeChan := make(chan int,2000)
	exitChan := make(chan bool,4)
	//开启协程 插入数据
	go putNum(intChan,100)

	//开启四个 计算协程
	for i:=1;i<=4;i++{
    
    
		go primeNum(intChan,primeChan,exitChan)
	}

	//当 exitchannel中取出四个元素后 证明任务执行完毕
	go func(){
    
    
		for i:=1;i<=4;i++{
    
    
			<-exitChan
		}
		close(primeChan)
	}()
	//遍历 primeChan
	fmt.Println("素数共",len(primeChan),"个如下:")
	for v:=range primeChan{
    
    
		fmt.Println(v)
	}

}
//放指定数量的数到channel中
func putNum(intChan chan int,num int){
    
    
	for i:=1;i<=num ;i++{
    
    
		intChan<-i
	}
	//添加完了 关闭 channel
	close(intChan)
}
//从 channel中取数据并判断是否为素数,如果是 则添加到 素数 channel中
//如果取不到数据了(数据一处理完)则 exitChannel中添加元素并 退出程序
func primeNum(intChan chan int,primeChan chan int,exitChan chan bool){
    
    
	var flag bool
	for{
    
    
		time.Sleep(time.Millisecond*10)//睡10毫秒
		num,ok:=<-intChan
		if !ok{
    
    
			break; //取不到数据 则 退出
		}
		flag =true
		for i:=2;i<num;i++{
    
    
			if num%i ==0{
    
    //证明是不是素数
				flag = false
				break
			}
		}
		if flag{
    
    //如果是素数则加到channel中
			primeChan<-num
		}

	}
	fmt.Println("一个协程由于取不到数据而退出")
	exitChan<-true//添加标识 到 退出 channel
}

読み取り専用および書き込み専用のコルーチンの例

func test05(){
    
    
	//默认情况下 channel是可读可写
	//声明为只写
	var chan2 chan<- int
	chan2 = make(chan int,3)
	chan2<- 1
	chan2<- 2
	//num:=<-chan2 //error invalid operation: 
	//声明只读channel
	//var chan3 <-chan int
	//chan3 = make(chan int,3)
	//chan3<- 1 //invalid operation: 
	//num:=<-chan3 
	//fmt.Println("num=",num)  // deadlock!

	//协程测试 只读 和只写
	 var ch chan int
	 ch  = make(chan int,10)
	 exitChan := make(chan bool,2)
	 go send(ch,exitChan)
	 go recv(ch,exitChan)
	//  go func(){
    
    
	// 	for i:=1;i<=2;i++ {
    
    
	// 		val :=<-exitChan
	// 		fmt.Println("执行结束")
	// 	 }
	// 	 close(exitChan)
	//  }()
	total:=0
	for v := range exitChan{
    
    
		fmt.Println("读取内容v=",v)
		total ++
		if(total>= 2){
    
    
			break
		}
	}
	
	 fmt.Println("执行结束")

}
//该方法只写数据到 channel
func send(ch chan<- int,exitChan chan bool){
    
    
	fmt.Println("send 开始")
	for i:=1;i<=10;i++{
    
    
		ch<-i
	}
	close(ch)
	fmt.Println("send 结束")
	exitChan<- true
}
//该方法只读数据
func recv(ch <-chan int,exitChan chan bool){
    
    
	fmt.Println("recv 开始")
	for {
    
    
		v,ok:=<-ch
		if !ok{
    
    
			fmt.Println("读取值错误",ok)
			break
		}
		fmt.Println("读取到值==》:",v)
	}
	fmt.Println("recv 结束")
	exitChan<-true
}

デッドロックの問題を解決するために選択する

func test06(){
    
    
   //select 解决从 管道 取数据的阻塞问题
   intChan :=make(chan int,10)
   for i:=0;i<10;i++{
    
    
	   intChan<-i
   }

   strChan :=make(chan string,5)
   for i:=0;i<5;i++{
    
    
	   strChan<-"hello"+fmt.Sprintf("%d",i)
   }

   //传统方式 遍历管道时 如果 没有关闭会导致阻塞 而报错 deadlock
   //实际开发过程中,可能不确实什么时候该关闭管道
   //可以使用select 解决报错的问题
   for{
    
    
	   select {
    
    
	   //这里 虽然intChan一直没有关闭,但是不会一直阻塞而deadlock,而是自动到写一个case匹配
	   		case v:=<-intChan:
				fmt.Printf("从intChan取值 %d \n",v)
				time.Sleep(time.Second)
			case v:=<-strChan:
				fmt.Printf("从strChan取值 %s \n",v)
				time.Sleep(time.Second)
			default:
				fmt.Printf("都没有取到值\n")
				return
	   }
   }

}

コルーチン例外処理

func test07(){
    
    
	go sayHello2()
	go testPanic()
	for i:=0;i<11;i++{
    
    
		fmt.Println("main ok~",i)
		time.Sleep(time.Second)
	}

}
func sayHello2(){
    
    
	for i:=0;i<10;i++{
    
    
		time.Sleep(time.Second)
		fmt.Println("helloWorld~")
	}
}
func testPanic(){
    
    
	//捕获异常
	//如果不捕获这个异常,那么主线程一起其他协程都会因异常而退出执行
	//捕获之后,该协程的异常不会影响其他协程和主线程
	defer func(){
    
    
		if err:=recover();err!=nil{
    
    
			fmt.Println("方法发生错误,",err)
		}
	}()
	var mapp map[int]string
	mapp[1]="golang"//没有执行make所以肯定报错
}

おすすめ

転載: blog.csdn.net/zhangxm_qz/article/details/115280572
おすすめ