真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Go素數(shù)篩選分析

Go素數(shù)篩選分析

1. 素數(shù)篩選介紹

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

目前創(chuàng)新互聯(lián)已為上千余家的企業(yè)提供了網(wǎng)站建設(shè)、域名、雅安服務(wù)器托管、網(wǎng)站托管維護(hù)、企業(yè)網(wǎng)站設(shè)計、薩迦網(wǎng)站維護(hù)等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。

素數(shù)篩選基本原理如下圖:

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

大佬的代碼如下:

// 返回生成自然數(shù)序列的管道: 2, 3, 4, ...
// GenerateNatural 函數(shù)內(nèi)部啟動一個 Goroutine 生產(chǎn)序列,返回對應(yīng)的管道
func GenerateNatural() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}
// 管道過濾器: 將輸入序列中是素數(shù)倍數(shù)的數(shù)淘汰,并返回新的管道
// 函數(shù)內(nèi)部啟動一個 Goroutine 生產(chǎn)序列,返回過濾后序列對應(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)的素數(shù)
		fmt.Printf("%v: %v\n", i+1, prime)
		ch = PrimeFilter(ch, prime) // 基于新素數(shù)構(gòu)造的過濾器
	}
}

main()函數(shù)先是調(diào)用 GenerateNatural() 生成最原始的從 2 開始的自然數(shù)序列。然后開始一個 100 次迭代的循環(huán),希望生成 100 個素數(shù)。在每次循環(huán)迭代開始的時候,管道中的第一個數(shù)必定是素數(shù),我們先讀取并打印這個素數(shù)。然后基于管道中剩余的數(shù)列,并以當(dāng)前取出的素數(shù)為篩子過濾后面的素數(shù)。不同的素數(shù)篩子對應(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) 是一個指針,所以我們能在函數(shù)間直接傳遞 channel,而不用傳遞 channel 的指針。

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

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

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

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

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

總結(jié)一下main()函數(shù)中,每循環(huán)1次,會增加一個協(xié)程PF(ch, prime),且協(xié)程Gen與新增加的協(xié)程之間是串聯(lián)的關(guān)系(即前一個協(xié)程的輸出,作為下一個協(xié)程的輸入,二者通過channel交互),協(xié)程main每次循環(huán)讀取最后一個channel的第1個值,獲取prime素數(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
	}
}

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

...
write 0xc0000
read 5 0xc0000
main() 1 0xc0000
main() 2 0xc000
write 0xc0000
read 5 0xc0000
...

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

(2) 素數(shù)篩選代碼驗(yàn)證

在之前素數(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
}

// 管道過濾器: 刪除能被素數(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)的素數(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) // 基于新素數(shù)構(gòu)造的過濾器
   }
}

1)打印協(xié)程id

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

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

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

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

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

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

接著,查看PrimeFilter(ch, prime)協(xié)程,如下。每輸出1個素數(shù),將增加1PrimeFilter(ch, prime)協(xié)程,且協(xié)程id號從3開始遞增。

[routineId: 0003]----read i=3, in=0xc0000, out=0xc0000
......
[routineId: 0004]----read i=5, in=0xc0000, 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=0xc0000值作為輸入,并將out=0xc0000輸出作為[routineId: 0004]協(xié)程輸入。以此類推,從id>=2開始的多個協(xié)程是通過channel管道串聯(lián)在一起的,且前一個協(xié)程的輸出作為后一個協(xié)程的輸入。與前述分析一致。

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

[routineId: 0002]----generate i=2, ch=0xc0000
[routineId: 0001]----main i=1; prime=2, ch=0xc0000, total=2
[routineId: 0003]----read i=3, in=0xc0000, out=0xc0000
......
[routineId: 0001]----main i=2; prime=3, ch=0xc0000, total=3
......
[routineId: 0004]----read i=5, in=0xc0000, 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é)

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

文章名稱:Go素數(shù)篩選分析
網(wǎng)頁網(wǎng)址:http://weahome.cn/article/dsoidho.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部