iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0

你是一名大廚,正在煮晚餐。你需要同時煮飯、烤魚、炒菜,而且還要確保每道菜在時間上都剛剛好。這就是 Concurrency ,讓你能像大廚一樣,同時處理多個任務,讓你的程序更高效、更快速。

Concurrency

來了,今天講 Concurrency !
並發 是指 同時執行 多個任務操作 的能力,而 不需要等待一個任務完成才能開始另一個 。在Golang中,可以使用 goroutine 實現 concurrency ,goroutine是一種輕量級的執行緒 Thread

Share by communicating

Share by communicating 指的是在 Golang 中,通過訊息來 共享數據 ,而不是通過共享內存來實現並發操作。這種方式有助於避免多線程中可能出現的競爭條件 Race Condition等問題。 在Golang中,這種概念通常使用通道(Channels)來實現。

舉例:

package main

import (
	"fmt"
	"time"
)

func main() {
	dataChannel := make(chan int) // 創建一個整數通道

	// 創建一個goroutine,將數據發送到通道
	go func() {
		for i := 1; i <= 5; i++ {
			fmt.Printf("客人進來第 %d 位\n", i)
			dataChannel <- i // 將數據發送到通道
			time.Sleep(time.Second)
		}
		close(dataChannel) // 關閉通道,表示不再發送數據
	}()

	// 主函數從通道讀取數據
	for num := range dataChannel {
		fmt.Printf("接待第 %d 位客人\n", num)
	}

	fmt.Println("沒位子了,下次請早!")
}
客人進來第 1 位
接待第 1 位客人
客人進來第 2 位
接待第 2 位客人
客人進來第 3 位
接待第 3 位客人
客人進來第 4 位
接待第 4 位客人
客人進來第 5 位
接待第 5 位客人
沒位子了,下次請早!

這種 Share by communicating 的方式確保了數據的 同步訪問 ,因為通道在不同goroutine之間傳遞數據時會 自動處理同步問題 ,從而減少了競爭條件的風險。

Goroutines

goroutine 是 Go 語言中用於實現並發的概念。每個 goroutine 都是一個 輕量級的執行緒 ,可以 獨立運行 ,但它們共享相同的地址空間。與傳統操作系統執行緒不同,goroutine 的創建和調度由 Go 的運行時系統自動處理,因此不需要擔心手動管理執行緒。

package main

import (
	"fmt"
	"time"
)

func sayHello() {
	for i := 1; i <= 5; i++ {
		fmt.Printf("歡迎光臨!第 %d 位客人\n", i)
		time.Sleep(time.Millisecond * 500)
	}
}

func main() {
	go sayHello() // 啟動一個 goroutine 執行 sayHello 函數

	// 主函數休眠一段時間,以確保 goroutine 有足夠的時間執行
	time.Sleep(time.Second * 3)

	fmt.Println("沒位子了,下次請早!")
}
歡迎光臨!第 1 位客人
歡迎光臨!第 2 位客人
歡迎光臨!第 3 位客人
歡迎光臨!第 4 位客人
歡迎光臨!第 5 位客人
沒位子了,下次請早!

Channels

channels 是 Golang 中的一個類型,它像一個 管道隊列 一樣,可以用於 goroutine 之間的 數據傳遞 。通道提供了一種安全的方式,讓一個 goroutine 能夠將數據 發送到通道 ,而另一個 goroutine 能夠從通道 接收數據 ,這樣可以確保數據在 goroutine 之間的 同步傳遞

package main

import "fmt"

func main() {
	messageChannel := make(chan string) // 創建一個字串通道

	go func() {
		messageChannel <- "Hello Channel!" // 發送一個字串到通道
	}()

	message := <-messageChannel // 從通道接收字串
	fmt.Println(message)

	close(messageChannel) // 關閉通道
}
Hello Channel!

Channels of channels

Channels of channels是指我們可以使用 通道 作為 另一個通道元素,從而建立 多層的通道結構 。這種機制可以管理並發程式碼,使通道成為傳遞和同步數據的載體。

package main

import (
	"fmt"
	"time"
)

func main() {
	// 創建一個通道的通道,每個元素都是一個通道,用於傳遞整數
	channelOfChannels := make(chan chan int, 3)

	// 創建三個 goroutine,每個 goroutine 都有一個獨立的整數通道,並將這些通道傳遞到 channelOfChannels 中
	for i := 0; i < 3; i++ {
		intChannel := make(chan int)
		go func(id int) {
			for j := 1; j <= 5; j++ {
				intChannel <- j * id
				time.Sleep(time.Millisecond * 300)
			}
			close(intChannel)
		}(i + 1)
		channelOfChannels <- intChannel
	}

	// 從 channelOfChannels 中讀取通道,並從這些通道中讀取數據
	for i := 0; i < 3; i++ {
		intChannel := <-channelOfChannels
		for num := range intChannel {
			fmt.Printf("從爐火 %d 起鍋上第 %d 道菜\n ", i+1, num)
		}
	}

	close(channelOfChannels)
}

從爐火 1 起鍋上第 1 道菜
從爐火 1 起鍋上第 2 道菜
從爐火 1 起鍋上第 3 道菜
從爐火 1 起鍋上第 4 道菜
從爐火 1 起鍋上第 5 道菜
從爐火 2 起鍋上第 2 道菜
從爐火 2 起鍋上第 4 道菜
從爐火 2 起鍋上第 6 道菜
從爐火 2 起鍋上第 8 道菜
從爐火 2 起鍋上第 10 道菜
從爐火 3 起鍋上第 3 道菜
從爐火 3 起鍋上第 6 道菜
從爐火 3 起鍋上第 9 道菜
從爐火 3 起鍋上第 12 道菜
從爐火 3 起鍋上第 15 道菜

Parallelization

Parallelization 是一種在多核處理器上 同時執行 多個任務 的方式,從而 加快 程序的 執行速度。在 Golang 中,平行處理是通過 同時運行多個 goroutine 來實現的,每個 goroutine 都可以在不同的核心上執行,並且它們可以通過channels 來進行通信和協調。

package main

import (
	"fmt"
	"sync"
)

func calculateSum(start, end int, wg *sync.WaitGroup, resultChan chan int) {
	defer wg.Done()

	sum := 0
	for i := start; i <= end; i++ {
		sum += i
	}

	resultChan <- sum
}

func main() {
	numTasks := 4
	resultChan := make(chan int, numTasks)
	var wg sync.WaitGroup

	for i := 0; i < numTasks; i++ {
		wg.Add(1)
		go calculateSum(i*25+1, (i+1)*25, &wg, resultChan)
	}

	go func() {
		wg.Wait()
		close(resultChan)
	}()

	totalSum := 0
	for partialSum := range resultChan {
		totalSum += partialSum
	}

	fmt.Printf("總和:%d\n", totalSum)
}
總和:5050

A leaky buffer

A leaky buffer是一種用於在生產者和消費者之間進行數據交換的機制。然而,如果緩衝區已滿,新的數據將仍然被接受,但這將導致緩衝區中的 舊數據被丟失 。這種緩衝區設計通常是不安全的,因為它可能導致內存泄漏或數據丟失,特別是在 高負載 情況下。

假設有一間餐廳只有5個座位。現在,有5個客人想要進來用餐但沒有預訂。當第6個客人來時將他安排入座,但這樣會導致一個問題「餐廳只有5個座位,現在需要讓一個現有的客人離開,否則無法容納所有的客人。」這導致會有一個客人被拒絕服務或者將一個客人趕出餐廳。

餐廳的座位數就好比 緩衝區的大小 ,客人就好比要存放在緩衝區中的 數據 。如果 緩衝區已滿,而新的數據仍然被接受,這可能導致一些數據被 覆蓋丟失 ,就像在餐廳中有可能讓一個現有的客人被拒絕服務一樣。這樣會導致數據錯誤或程序崩潰。

package main

import (
	"fmt"
	"time"
)

func main() {
	bufferSize := 2
	dataChannel := make(chan int, bufferSize)

	go func() {
		for i := 1; i <= 5; i++ {
			dataChannel <- i // 將數據發送到有漏洞的緩衝區
			fmt.Printf("點餐%d\n", i)
			time.Sleep(time.Millisecond * 500)
		}
		close(dataChannel)
	}()

	for num := range dataChannel {
		fmt.Printf("開始煮%d\n", num)
		time.Sleep(time.Millisecond * 1000)
	}
}
點餐1
開始煮1
點餐2
開始煮2
點餐3
點餐4
開始煮3
點餐5
開始煮4
開始煮5

碎語

上班同時打game、傳line、達標OKR,concurrency人,你是自己的超人。


上一篇
09 | 正衣冠、知興替、明得失
下一篇
11 | 因果何曾饒過誰
系列文
Go 語言學習手札30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言