欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go素?cái)?shù)篩選分析詳解

 更新時(shí)間:2022年10月19日 09:33:43   作者:happyfuns  
學(xué)習(xí)Go語言的過程中,遇到素?cái)?shù)篩選的問題。這是一個(gè)經(jīng)典的并發(fā)編程問題,是某大佬的代碼,短短幾行代碼就實(shí)現(xiàn)了素?cái)?shù)篩選,這篇文章主要介紹了Go素?cái)?shù)篩選分析,需要的朋友可以參考下

Go素?cái)?shù)篩選分析

1. 素?cái)?shù)篩選介紹

學(xué)習(xí)Go語言的過程中,遇到素?cái)?shù)篩選的問題。這是一個(gè)經(jīng)典的并發(fā)編程問題,是某大佬的代碼,短短幾行代碼就實(shí)現(xiàn)了素?cái)?shù)篩選。但是自己看完原理和代碼后一臉懵逼(僅此幾行能實(shí)現(xiàn)素?cái)?shù)篩選),然后在網(wǎng)上查詢相關(guān)資料,依舊似懂非懂。經(jīng)過1天的分析調(diào)試,目前基本上掌握了的原理。在這里介紹一下學(xué)習(xí)理解的過程。

素?cái)?shù)篩選基本原理如下圖:

就原理來說還是比較簡(jiǎn)單的,首先生成從 2 開始的遞增自然數(shù),然后依次對(duì)生成的第 1, 2, 3, ...個(gè)素?cái)?shù) 整除,經(jīng)過全部整除仍有余數(shù)的自然數(shù),將會(huì)是素?cái)?shù)。

大佬的代碼如下:

// 返回生成自然數(shù)序列的管道: 2, 3, 4, ...
// GenerateNatural 函數(shù)內(nèi)部啟動(dòng)一個(gè) Goroutine 生產(chǎn)序列,返回對(duì)應(yīng)的管道
func GenerateNatural() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}
// 管道過濾器: 將輸入序列中是素?cái)?shù)倍數(shù)的數(shù)淘汰,并返回新的管道
// 函數(shù)內(nèi)部啟動(dòng)一個(gè) Goroutine 生產(chǎn)序列,返回過濾后序列對(duì)應(yīng)的管道
func PrimeFilter(in <-chan int, prime int) chan int {
	out := make(chan int)
	go func() {
		for {
			if i := <-in; i%prime != 0 {
				out <- i
			}
		}
	}()
	return out
}
func main() {
	ch := GenerateNatural() // 自然數(shù)序列: 2, 3, 4, ...
	for i := 0; i < 100; i++ {
		prime := <-ch // 新出現(xiàn)的素?cái)?shù)
		fmt.Printf("%v: %v\n", i+1, prime)
		ch = PrimeFilter(ch, prime) // 基于新素?cái)?shù)構(gòu)造的過濾器
	}
}

main()函數(shù)先是調(diào)用 GenerateNatural() 生成最原始的從 2 開始的自然數(shù)序列。然后開始一個(gè) 100 次迭代的循環(huán),希望生成 100 個(gè)素?cái)?shù)。在每次循環(huán)迭代開始的時(shí)候,管道中的第一個(gè)數(shù)必定是素?cái)?shù),我們先讀取并打印這個(gè)素?cái)?shù)。然后基于管道中剩余的數(shù)列,并以當(dāng)前取出的素?cái)?shù)為篩子過濾后面的素?cái)?shù)。不同的素?cái)?shù)篩子對(duì)應(yīng)的管道是串聯(lián)在一起的。

運(yùn)行代碼,程序正確輸出如下:

1: 2
2: 3
3: 5
......
......
98: 521
99: 523
100: 541

2. 代碼分析

之前在課本中學(xué)習(xí)到:chan底層結(jié)構(gòu) 是一個(gè)指針,所以我們能在函數(shù)間直接傳遞 channel,而不用傳遞 channel 的指針

上述代碼fun GenerateNatural()中創(chuàng)建了管道ch := make(chan int),并創(chuàng)建一個(gè)協(xié)程(為了便于描述,該協(xié)程稱為Gen)持續(xù)向ch中寫入漸增自然數(shù)。

當(dāng)i=0時(shí),main()prime := <-ch讀取該ch(此時(shí)prime=2,輸出素?cái)?shù)2),接著將ch傳入PrimeFilter(ch, prime)中。PrimeFilter(ch, prime)創(chuàng)建新協(xié)程(稱為PF(ch, 2))持續(xù)讀取傳入的chch2之前已被取出,從3依次往后讀取),同時(shí)返回一個(gè)新的chan out(當(dāng)通過過濾器的iout寫入時(shí),此時(shí)out僅有寫入而沒有讀取操作,PF(ch, 2)將阻塞在第1次寫chan out操作)。與此同時(shí)main()ch = PrimeFilter(ch, 2)out賦值給ch,此操作給ch賦了新變量。到這里,重點(diǎn)來了:由于在隨后的時(shí)間里,協(xié)程Gen、PF(ch, 2)中仍需要不停寫入和讀取ch,這里將out賦值給ch的操作是否會(huì)更改Gen、PF(ch, 2)兩協(xié)程中ch的值了?

直接給出答案(后面會(huì)給出代碼測(cè)試),此時(shí)ch賦新值不影響Gen、PF(ch, 2)兩協(xié)程,僅影響main() for循環(huán)體隨后對(duì)chan的操作。(本人認(rèn)為gochannel參數(shù)傳遞采用了channel指針的拷貝,后續(xù)給channel賦新值相當(dāng)于將該channel重新指向了另外一個(gè)地址,該channel與之前協(xié)程中使用的channel分別指向不同地址,是完全不同的變量)。為了便于后面分析,這里將ch = PrimeFilter(ch, 2)賦值后的ch稱為ch_2。

當(dāng)i=1時(shí),main() for循環(huán)讀取前一次產(chǎn)生新的ch_2賦值給prime(此時(shí)prime=3,輸出素?cái)?shù)3),接著將ch_2傳入PrimeFilter(ch, prime)并創(chuàng)建新協(xié)程(稱為PF(ch, 3)),而后ch = PrimeFilter(ch, 3)將新產(chǎn)生的out賦值給ch,稱為ch_3。與此同時(shí)協(xié)程Gen持續(xù)向ch中寫入直至阻塞,攜程PF(ch, 2)持續(xù)讀取ch值并寫入ch_2直至阻塞,新協(xié)程PF(ch, 3)持續(xù)讀取ch_2值并輸出至chan out(即ch_3)(此時(shí)ch_3僅有寫入而沒有讀取操作,PF(ch, 3)將阻塞在第1次寫ch_3操作)。

當(dāng)i繼續(xù)增加時(shí),后面的結(jié)果以此類推。

總結(jié)一下:main()函數(shù)中,每循環(huán)1次,會(huì)增加一個(gè)協(xié)程PF(ch, prime),且協(xié)程Gen與新增加的協(xié)程之間是串聯(lián)的關(guān)系(即前一個(gè)協(xié)程的輸出,作為下一個(gè)協(xié)程的輸入,二者通過channel交互),協(xié)程main每次循環(huán)讀取最后一個(gè)channel的第1個(gè)值,獲取prime素?cái)?shù)?;驹砣缦聢D所示。

3. 代碼驗(yàn)證

(1) channel參數(shù)傳遞驗(yàn)證

func main() {
	ch1 := make(chan int)
	go write(ch1)
	go read(ch1)
	time.Sleep(time.Second * 3)
	fmt.Println("main() 1", ch1)
	ch2 = make(chan int)
    ch1 = ch2
	fmt.Println("main() 2", ch1)
	time.Sleep(time.Second * 3)
}

func read(ch1 chan int) {
	for {
		time.Sleep(time.Second)
		fmt.Println("read", <-ch1, ch1)
	}
}
func write(ch1 chan int) {
	for {
		time.Sleep(time.Second)
		fmt.Println("write", ch1)
		ch1 <- 5
	}
}

測(cè)試代碼比較簡(jiǎn)單,在main()中創(chuàng)建chan ch1,后創(chuàng)建兩個(gè)協(xié)程writeread分別對(duì)ch1不間斷寫入與讀取,持續(xù)一段時(shí)間后,main()新創(chuàng)建ch2,并賦值給ch1,查看協(xié)程writeread是否受到影響。

...
write 0xc000048120
read 5 0xc000048120
main() 1 0xc000048120
main() 2 0xc000112000
write 0xc000048120
read 5 0xc000048120
...

程序輸出如上,可以看到ch1地址為0xc000048120,ch2地址為0xc000112000。main()ch1的重新賦值不會(huì)影響到其他協(xié)程對(duì)ch1的讀寫。

(2) 素?cái)?shù)篩選代碼驗(yàn)證

在之前素?cái)?shù)篩選源碼的基礎(chǔ)上,添加一些調(diào)試打印代碼,以便更容易分析代碼,如下所示。

package main

import (
   "fmt"
   "runtime"
   "sync/atomic"
)

var total uint32

// 返回生成自然數(shù)序列的管道: 2, 3, 4, ...
func GenerateNatural() chan int {
   ch := make(chan int)
   go func() {
      goRoutineId := atomic.AddUint32(&total, 1)
      for i := 2; ; i++ {
         //fmt.Println("before generate", i)
         ch <- i
         fmt.Printf("[routineId: %.4v]----generate i=%v, ch=%v\n", goRoutineId, i, ch)
      }
   }()
   return ch
}

// 管道過濾器: 刪除能被素?cái)?shù)整除的數(shù)
func PrimeFilter(in <-chan int, prime int) chan int {
   out := make(chan int)
   go func() {
      goRoutineId := atomic.AddUint32(&total, 1)
      for {
         i := <-in
         if i%prime != 0 {
            fmt.Printf("[routineId: %.4v]----read i=%v, in=%v, out=%v\n", goRoutineId, i, in, out)
            out <- i
         }
      }
   }()
   return out
}

func main() {
   goRoutineId := atomic.AddUint32(&total, 1)
   ch := GenerateNatural() // 自然數(shù)序列: 2, 3, 4, ...
   for i := 0; i < 100; i++ {
      //fmt.Println("--------before read prime")
      prime := <-ch // 新出現(xiàn)的素?cái)?shù)
      fmt.Printf("[routineId: %.4v]----main i=%v; prime=%v, ch=%v, total=%v\n", goRoutineId, i+1, prime, ch, runtime.NumGoroutine())
      ch = PrimeFilter(ch, prime) // 基于新素?cái)?shù)構(gòu)造的過濾器
   }
}

1)打印協(xié)程id

由于Go語言沒有直接把獲取goid的接口暴露出來,這里采用atomic.AddUint32原子操作,每次新建1個(gè)協(xié)程時(shí),將atomic.AddUint32(&total, 1)的值保存下來,作為該協(xié)程的唯一id。

2)輸出結(jié)果分析

[routineId: 0002]----generate i=2, ch=0xc000018180
[routineId: 0001]----main i=1; prime=2, ch=0xc000018180, total=2
[routineId: 0003]----read i=3, in=0xc000018180, out=0xc000090000
[routineId: 0002]----generate i=3, ch=0xc000018180
[routineId: 0001]----main i=2; prime=3, ch=0xc000090000, total=3
[routineId: 0002]----generate i=4, ch=0xc000018180
[routineId: 0002]----generate i=5, ch=0xc000018180
[routineId: 0003]----read i=5, in=0xc000018180, out=0xc000090000
[routineId: 0002]----generate i=6, ch=0xc000018180
[routineId: 0002]----generate i=7, ch=0xc000018180
......

輸出結(jié)果如上,main協(xié)程id=1,GenerateNatural協(xié)程id=2,PrimeFilter(ch, prime)協(xié)程id3開始遞增。這里還是不太容易看明白,下面分類闡述輸出結(jié)果。

首先,單獨(dú)查看GenerateNatural協(xié)程輸出,如下??梢钥闯觯藚f(xié)程就是在寫入阻塞交替間往ch=0xc000018180中寫入數(shù)據(jù)。

[routineId: 0002]----generate i=2, ch=0xc000018180
[routineId: 0002]----generate i=3, ch=0xc000018180
[routineId: 0002]----generate i=4, ch=0xc000018180
[routineId: 0002]----generate i=5, ch=0xc000018180
[routineId: 0002]----generate i=6, ch=0xc000018180
[routineId: 0002]----generate i=7, ch=0xc000018180
[routineId: 0002]----generate i=8, ch=0xc000018180
[routineId: 0002]----generate i=9, ch=0xc000018180
......

接著,查看PrimeFilter(ch, prime)協(xié)程,如下。每輸出1個(gè)素?cái)?shù),將增加1個(gè)PrimeFilter(ch, prime)協(xié)程,且協(xié)程id號(hào)從3開始遞增。

[routineId: 0003]----read i=3, in=0xc000018180, out=0xc000090000
......
[routineId: 0004]----read i=5, in=0xc000090000, out=0xc0000181e0
......
[routineId: 0005]----read i=7, in=0xc0000181e0, out=0xc00020a000
......
[routineId: 0006]----read i=11, in=0xc00020a000, out=0xc00020a060
......

可以看出,協(xié)程[routineId: 0003]讀取GenerateNatural協(xié)程ch=0xc000018180值作為輸入,并將out=0xc000090000輸出作為[routineId: 0004]協(xié)程輸入。以此類推,從id>=2開始的多個(gè)協(xié)程是通過channel管道串聯(lián)在一起的,且前一個(gè)協(xié)程的輸出作為后一個(gè)協(xié)程的輸入。與前述分析一致。

最后,查看main線程,其id=1,可見main每次循環(huán)讀取最后一個(gè)channel的第1個(gè)值,且該值為素?cái)?shù)。與前述分析一致。

[routineId: 0002]----generate i=2, ch=0xc000018180
[routineId: 0001]----main i=1; prime=2, ch=0xc000018180, total=2
[routineId: 0003]----read i=3, in=0xc000018180, out=0xc000090000
......
[routineId: 0001]----main i=2; prime=3, ch=0xc000090000, total=3
......
[routineId: 0004]----read i=5, in=0xc000090000, out=0xc0000181e0
......
[routineId: 0001]----main i=3; prime=5, ch=0xc0000181e0, total=4
[routineId: 0005]----read i=7, in=0xc0000181e0, out=0xc00020a000
[routineId: 0001]----main i=4; prime=7, ch=0xc00020a000, total=5

4. 總結(jié)

  • 對(duì)Go不同協(xié)程中chan的傳遞原理了解不深,且素?cái)?shù)篩選代碼中多個(gè)協(xié)程統(tǒng)一使用了ch名稱,特別是對(duì)于main()中ch的重新賦值會(huì)不會(huì)影響其他協(xié)程不甚了解,導(dǎo)致理解混亂。
  • 經(jīng)深入分析代碼后理解了素?cái)?shù)篩選的內(nèi)部原理,可謂知其所以然,然如果讓自己來設(shè)計(jì),代碼肯定會(huì)臃腫非常多,對(duì)于大佬能用如此簡(jiǎn)單的代碼實(shí)現(xiàn)功能,萬分欽佩!

到此這篇關(guān)于Go素?cái)?shù)篩選分析的文章就介紹到這了,更多相關(guān)Go素?cái)?shù)篩選內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go json數(shù)據(jù)轉(zhuǎn)發(fā)的實(shí)現(xiàn)代碼

    go json數(shù)據(jù)轉(zhuǎn)發(fā)的實(shí)現(xiàn)代碼

    這篇文章主要介紹了go json數(shù)據(jù)轉(zhuǎn)發(fā)的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Golang10進(jìn)制轉(zhuǎn)16進(jìn)制的幾種方法代碼示例

    Golang10進(jìn)制轉(zhuǎn)16進(jìn)制的幾種方法代碼示例

    這篇文章主要給大家介紹了關(guān)于Golang10進(jìn)制轉(zhuǎn)16進(jìn)制的幾種方法,進(jìn)制轉(zhuǎn)換是Golang的一些基本操作,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • golang監(jiān)聽文件變化的實(shí)例

    golang監(jiān)聽文件變化的實(shí)例

    這篇文章主要介紹了golang監(jiān)聽文件變化的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • golang package time的用法具體詳解

    golang package time的用法具體詳解

    本篇文章主要介紹了golang package time的用法具體詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • Go語言中日期包(time包)的具體使用

    Go語言中日期包(time包)的具體使用

    本文主要介紹了Go語言中日期包的具體使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • go語言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制

    go語言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制

    Casbin是用于Golang項(xiàng)目的功能強(qiáng)大且高效的開源訪問控制庫(kù)。本文主要介紹了go語言使用Casbin實(shí)現(xiàn)角色的權(quán)限控制,感興趣的可以了解下
    2021-06-06
  • GO中的slice使用簡(jiǎn)介(源碼分析slice)

    GO中的slice使用簡(jiǎn)介(源碼分析slice)

    slice(切片)是go中常見和強(qiáng)大的類型,這篇文章不是slice使用簡(jiǎn)介,從源碼角度來分析slice的實(shí)現(xiàn),slice的一些迷惑的使用方式,感興趣的朋友跟隨小編一起看看吧
    2023-06-06
  • 掌握GoLang Fiber路由和中間件技術(shù)進(jìn)行高效Web開發(fā)

    掌握GoLang Fiber路由和中間件技術(shù)進(jìn)行高效Web開發(fā)

    這篇文章主要為大家介紹了GoLang Fiber路由和中間件進(jìn)行高效Web開發(fā),本文將深入探討 Fiber 中的路由細(xì)節(jié),學(xué)習(xí)如何創(chuàng)建和處理路由,深入了解使用路由參數(shù)的動(dòng)態(tài)路由,并掌握在 Fiber 應(yīng)用程序中實(shí)現(xiàn)中間件的藝術(shù)
    2024-01-01
  • Go語言標(biāo)準(zhǔn)庫(kù)sync.Once使用場(chǎng)景及性能優(yōu)化詳解

    Go語言標(biāo)準(zhǔn)庫(kù)sync.Once使用場(chǎng)景及性能優(yōu)化詳解

    這篇文章主要為大家介紹了Go語言標(biāo)準(zhǔn)庫(kù)sync.Once使用場(chǎng)景及性能優(yōu)化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Golang實(shí)現(xiàn)AES加密和解密的示例代碼

    Golang實(shí)現(xiàn)AES加密和解密的示例代碼

    AES( advanced encryption standard)使用相同密鑰進(jìn)行加密和解密,也就是對(duì)稱加密。本文將詳細(xì)講解Golang實(shí)現(xiàn)AES加密和解密的方法,感興趣的可以學(xué)習(xí)一下
    2022-05-05

最新評(píng)論