亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go并發的方法有哪些

發布時間:2022-04-06 17:33:50 來源:億速云 閱讀:126 作者:iii 欄目:開發技術

這篇文章主要介紹了Go并發的方法有哪些的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Go并發的方法有哪些文章都會有所收獲,下面我們一起來看看吧。

一、goroutine

1、協程(Coroutine)

Golang 在語言層面對并發編程進行了支持,使用了一種協程(goroutine)機制,

協程本質上是一種用戶態線程,不需要操作系統來進行搶占式調度,但是又寄生于線程中,因此系統開銷極小,可以有效的提高線程的任務并發性,而避免多線程的缺點。但是協程需要語言上的支持,需要用戶自己實現調度器,因為在Go語言中,實現了調度器所以我們可以很方便的能過 go關鍵字來使用協程。

func main() {
	for i := 0; i <10; i++ {
		go func(i int) {
			for  {
				fmt.Printf("Hello goroutine %d\n",i)
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
}

最簡單的一個并發編程小例子,并發輸出一段話。

我們同時開了10個協程進行輸出,每次在fmt.printf時交出控制權(不一定每次都會交出控制權),回到調度器中,再由調度器分配。

2、goroutine 可能切換的點

  • I/O,Select

  • channel

  • 等待鎖

  • 函數調用

  • runtime.Gosched()

我們看一個小例子:

func main() {
	var a [10]int
	for i := 0; i <10; i++ {
		go func(i int) {
			for  {
				a[i]++
			}
		}(i)
	}
	time.Sleep(time.Millisecond)
	fmt.Println(a)
}

在這里,代碼直接鎖死,程序沒有退出,因為在執行函數中沒有協程的切換,因為 main函數也是一個協程。

如果想要程序退出,可以通過 runtime.Gosched()函數,在執行函數中添加一行。

for  {
  a[i]++
  runtime.Gosched()
}

加上這個函數之后,代碼是可以正常執行了,但是真的是正常執行嗎?不一定,我們可以使用 -reac命令來看一下數據是否有沖突:

Go并發的方法有哪些

這說明數據還是有沖突的,數組a中的元素一邊在做自增,一邊在輸出。解決這個問題,我們只能使用 channel 來解決。

二、Channel

Channel 中 Go語言在語言級別提供了對 goroutine 之間通信的支持,我們可以使用 channel 在兩個或者多個goroutine之間進行信息傳遞,能過 channel 傳遞對像的過程和調用函數時的參數傳遞行為一樣,可以傳遞普通參數和指針。

Channel 有兩種模式:

var ch2 = make(chan int)		// 無緩沖 channel,同步
var ch3 = make(chan int, 2)	// 有緩沖 channel, 異步

無緩沖的方式,數據進入 channel 只要沒有被接收,就會處在阻塞狀態。

var ch2 = make(chan int)		// 無緩沖 channel,同步
ch2 <- 1
ch2 <- 2
//  error: all goroutines are asleep - deadlock!
fmt.Println(<-ch2)

如果想要運行,必須要再開一個協程不停的去請求數據:

var ch2 = make(chan int)		// 無緩沖 channel,同步
go func() {
  for  {
    n := <-ch2
    fmt.Println(n)
  }
}()
ch2 <- 1
ch2 <- 2

有緩沖的方式,只要緩沖區沒有滿就可以一直進數據,緩沖區在填滿之后沒有接收也會處理阻塞狀態。

func bufferChannel() {
	var ch3 = make(chan int,2)
	ch3<-1
	ch3<-2
	fmt.Println(ch3)
  // 不加這一行的話,是可以正常運行的
	ch3<-3			// error: all goroutines are asleep - deadlock!
}

1、chaanel 指定方向

比如我現在有一個函數創建一個 channel,并且不斷的需要消費channel中的數據:

func worker(ch chan int) {
	for {
		fmt.Printf("hello goroutine worker %d\n", <-ch)
	}
}

func createWorker() chan int{
	ch := make(chan int)
	go worker(ch)
	return ch
}

func main() {
	ch := createWorker()
	ch<-1
	ch<-2
	ch<-3
	time.Sleep(time.Millisecond)
}

這個函數我是要給別人用的,但是我怎么保證使用 createWorker 函數創建的 channel 都是往里面傳入數據的呢?

如果外面有人消費了這個 channel 中的數據,我們怎么限制?

這個時候,我們就可以給返回的channel 加上方向,指明這個 channel 中能往里傳入數據,不能從中取數據:

func worker(ch <-chan int) {
	for {
		fmt.Printf("hello goroutine worker %d\n", <-ch)
	}
}

func createWorker() chan<- int{
	ch := make(chan int)
	go worker(ch)
	return ch
}

我們可以在返回 channel 的地方加上方向,指明返回的函數只能是一個往里傳入數據,不能從中取數據。

并且我們還可以給專門消費的函數加上一個方向,指明這個函數只能出不能進。

2、channel 關閉

在使用 channel 的時候,隨說我們可以等待channel中的函數使用完之后自己結束,或者等待 main 函數結束時關閉所有的 goroutine 函數,但是這樣的方式顯示不夠優雅。

當一個數據我們明確知道他的結束時候,我們可以發送一個關閉信息給這個 channel ,當這個 channel 接收到這個信號之后,自己關閉。

// 方法一
func worker(ch <-chan int) {
	for {
		if c ,ok := <- ch;ok{
			fmt.Printf("hello goroutine worker %d\n", c)
		}else {
			break
		}
	}
}
// 方法二
func worker(ch <-chan int) {
	for c := range ch{
		fmt.Printf("hello goroutine worker %d\n", c)
	}
}

func main() {
	ch := createWorker()
	ch<-1
	ch<-2
	ch<-3
	close(ch)
	time.Sleep(time.Millisecond)
}

通過 Closeb函數,我們可以能過 channel 已經關閉,并且我們還可以通過兩種方法判斷通道內是否還有值。

三、Select

當我們在實際開發中,我們一般同時處理兩個或者多個 channel 的數據,我們想要完成一個那個 channel 先來數據,我們先來處理個那 channel 怎么辦呢?

此時,我們就可以使用 select 調度:

func genInt() chan int {
	ch := make(chan int)
	go func() {
		i := 0
		for {
			// 隨機兩秒以內生成一次數據
			time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
			ch <- i
			i++
		}
	}()
	return ch
}

func main() {
	var c1 = genInt()
	var c2 = genInt()
	for {
		select {
		case n := <-c1:
			fmt.Printf("server 1 generator %d\n", n)
		case n := <- c2:
			fmt.Printf("server 2 generator %d\n", n)
		}
	}
}

1、定時器

	for {
		tick := time.Tick(time.Second)
		select {
		case n := <-c1:
			fmt.Printf("server 1 generator %d\n", n)
		case n := <-c2:
			fmt.Printf("server 2 generator %d\n", n)
		case <-tick:
			fmt.Println("定時每秒輸出一次!")
		}
	}

2、超時

	for {
		tick := time.Tick(time.Second)
		select {
		case n := <-c1:
			fmt.Printf("server 1 generator %d\n", n)
		case n := <-c2:
			fmt.Printf("server 2 generator %d\n", n)
		case <-tick:
			fmt.Println("定時每秒輸出一次!")
		case <-time.After(1300 * time.Millisecond): // 如果 1.3秒內沒有數據進來,那么就輸出超時
			fmt.Println("timeout")
		}
	}

四、傳統的并發控制

1、sync.Mutex

type atomicInt struct {
	value int
	lock sync.Mutex
}

func (a *atomicInt) increment() {
	a.lock.Lock()
	defer a.lock.Unlock()		// 使用 defer 解鎖,以防忘記
	a.value++
}

func main() {
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Millisecond)
	fmt.Println(a.value)
}

2、sync.WaitGroup

type waitGrouInt struct {
	value int
	wg sync.WaitGroup
}

func (w *waitGrouInt) addInt() {
	w.wg.Add(1)
	w.value++
}

func main() {
	var w waitGrouInt
	for i := 0; i < 10; i++ {
		w.addInt()
		w.wg.Done()
	}
	w.wg.Wait()
	fmt.Println(w.value)
}

關于“Go并發的方法有哪些”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Go并發的方法有哪些”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

go
AI

张家川| 南昌市| 桂林市| 泰州市| 镇江市| 沁源县| 石家庄市| 游戏| 察隅县| 自治县| 重庆市| 鹤山市| 新巴尔虎左旗| 利辛县| 大宁县| 两当县| 兴隆县| 普格县| 杭州市| 射阳县| 乐昌市| 景东| 五原县| 龙海市| 平潭县| 灵川县| 襄城县| 皮山县| 申扎县| 固始县| 专栏| 乐昌市| 定南县| 开化县| 嘉善县| 汝州市| 海晏县| 西华县| 股票| 麦盖提县| 德阳市|