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

一文教你Golang如何正確關閉通道

 更新時間:2023年10月31日 10:43:16   作者:燈火消逝的碼頭  
Go在通道這一塊,沒有內(nèi)置函數(shù)判斷通道是否已經(jīng)關閉,也沒有可以直接獲取當前通道數(shù)量的方法,因此如果對通道進行了錯誤的使用,將會直接引發(fā)系統(tǒng)?panic,這是一件很危險的事情,下面我們就來學習一下如何正確關閉通道吧

序言

Go 在通道這一塊,沒有內(nèi)置函數(shù)判斷通道是否已經(jīng)關閉,也沒有可以直接獲取當前通道數(shù)量的方法。所以對于通道,Go 顯示的不是那么優(yōu)雅。另外,如果對通道進行了錯誤的使用,將會直接引發(fā)系統(tǒng) panic,這是一件很危險的事情。

如何判斷通道是否關閉

雖然沒有判斷通道是否關閉的內(nèi)置函數(shù),但是官方為我們提供了一種語法來判斷通道是否關閉:

v, ok := <-ch
// 如果ok為true則代表通道已經(jīng)關閉

利用這個語法,我們可以編寫這樣的代碼判斷通道是否關閉:

func TestChanClosed(t *testing.T) {
	var ch = make(chan int)

	// send
	go func() {
		for {
			ch <- 1
		}
	}()

	// receive
	go func() {
		for {
			if v, ok := <-ch; ok {
				t.Log(v)
			} else {
				t.Log("通道關閉")
				return
			}
		}
	}()

	time.Sleep(1 * time.Second)
}

也可以用 for range 簡化語法,通道關閉后會主動退出 for 循環(huán):

func TestChanClosed(t *testing.T) {
	var ch = make(chan int)

	// send
	go func() {
		for {
			ch <- 1
		}
	}()

	// receive
	go func() {
		for v := range ch {
			t.Log(v)
		}
		t.Log("通道關閉")
		return
	}()

	time.Sleep(1 * time.Second)
}

什么樣的情況會 panic

有三種情況會引發(fā) panic:

// 會引發(fā)channel panic的情況一:發(fā)送數(shù)據(jù)到已經(jīng)關閉的channel
// panic: send on closed channel
func TestChannelPanic1(t *testing.T) {
	var ch = make(chan int)
	close(ch)
	time.Sleep(10 * time.Millisecond)
	go func() {
		ch <- 1
	}()
	t.Log(<-ch)
}

// 會引發(fā)channel panic的情況一的另外一種:發(fā)送數(shù)據(jù)時關閉channel
// panic: send on closed channel
func TestChannelPanic11(t *testing.T) {
	var ch = make(chan int)
	go func() {
		go func() {
			// 沒有接收數(shù)據(jù)的地方,此處會一直阻塞
			ch <- 1
		}()
	}()

	time.Sleep(20 * time.Millisecond)
	close(ch)
}

// 會引發(fā)channel panic的情況二:重復關閉channel
// panic: close of closed channel
func TestChannelPanic2(t *testing.T) {
	var ch = make(chan int)
	close(ch)
	close(ch)
}

// 會引發(fā)channel panic的情況三:未初始化關閉
// panic: close of nil channel
func TestChannelPanic3(t *testing.T) {
	var ch chan int
	close(ch)
}

我們在實際的業(yè)務中應該避免這三種不同的 panic,未初始化就關閉的情況較為少見,也不容易犯錯誤,重要的是要防止關閉后發(fā)送數(shù)據(jù)和重復關閉通道。

如何避免 panic

在 go 中有一條原則:Channel Closing Principle,它是指不要從接收端關閉 channel,也不要關閉有多個并發(fā)發(fā)送者的 channel。只要我們嚴格遵守這個原則,就可以有效的避免panic。其實這個原則就是讓我們規(guī)避關閉后發(fā)送重復關閉這兩種情況。

為了應對關閉后發(fā)送數(shù)據(jù)這種情況,我們很容易想到Channel Closing Principle的第一句:不要從接收端關閉 channel。所以我們應該從發(fā)送端關閉 channel:

func TestSendClose(t *testing.T) {
	var (
		ch = make(chan int)
		wg = sync.WaitGroup{}
		// 10毫秒后通知發(fā)送端停止發(fā)送數(shù)據(jù)
		after = time.After(10 * time.Millisecond)
	)
	wg.Add(2)

	// send
	go func() {
		for {
			select {
			case <-after:
				close(ch)
				wg.Done()
				return
			default:
				ch <- 1
			}
		}
	}()

	// receive
	go func() {
		defer wg.Done()
		for v := range ch {
			t.Log(v)
		}
		return
	}()

	wg.Wait()
}

這種方式可以應對單發(fā)送者的情況,如果我們的程序有多個發(fā)送者,那么就要考慮Channel Closing Principle的第二句話:不要關閉有多個并發(fā)發(fā)送者的 channel。那么這種情況下,我們應該如何正確的回收通道呢?這個時候我們可以考慮引入一個額外的通道,當接收端不想再接收數(shù)據(jù)時,就發(fā)送數(shù)據(jù)到這個額外的通道中,來通知所有的發(fā)送端退出:

func TestManySendAndOneReceive(t *testing.T) {
	var (
		sender = 3
		wg     = sync.WaitGroup{}
		numCh  = make(chan int)
		stopCh = make(chan struct{})
		// 10毫秒后通知發(fā)送端停止發(fā)送數(shù)據(jù)
		after = time.After(10 * time.Millisecond)
	)
	wg.Add(1)

	// send
	for i := 0; i < sender; i++ {
		go func() {
			for {
				select {
				case <-stopCh:
					fmt.Println("收到退出信號")
					return
				case numCh <- 1:
					//fmt.Println("發(fā)送成功", value)
				}
			}
		}()
	}

	// receive
	go func() {
		for {
			select {
			case v := <-numCh:
				fmt.Println("接收到數(shù)據(jù)", v)
			case <-after:
				close(stopCh)
				wg.Done()
				return
			}
		}
	}()

	wg.Wait()
}

看完這段代碼,我們發(fā)現(xiàn) numCh 這個通道是沒有關閉語句的,那么這段代碼會引發(fā)內(nèi)存泄漏嗎?答案是不會,因為我們正確退出了發(fā)送端和接收端的所有協(xié)程,等到這個通道沒有任何代碼使用后,Go 的垃圾回收會回收此通道。

那如果此時我們的程序變得更為復雜:有多個接收者和多個發(fā)送者,這個時候怎么辦呢?我們可以引入另外一個中間者,當任意協(xié)程想關閉的時候,都通知這個中間者,所有協(xié)程也同時監(jiān)聽這個中間者,收到中間者的退出信號時,退出當前協(xié)程:

func TestManySendAndManyReceive(t *testing.T) {
	var (
		maxRandomNumber = 5000
		receiver        = 10
		sender          = 10
		wg              = sync.WaitGroup{}
		numCh           = make(chan int)
		stopCh          = make(chan struct{})
		toStop          = make(chan string, 1)
		stoppedBy       string
	)
	wg.Add(receiver)

	// moderator
	go func() {
		stoppedBy = <-toStop
		close(stopCh)
	}()

	// senders
	for i := 0; i < sender; i++ {
		go func(id string) {
			for {
				value := rand.Intn(maxRandomNumber)
				if value == 0 {
					select {
					case toStop <- "sender#" + id:
					default:
					}
					return
				}

				// 提前關閉goroutine
				select {
				case <-stopCh:
					return
				default:
				}

				select {
				case <-stopCh:
					return
				case numCh <- value:
				}
			}
		}(strconv.Itoa(i))
	}

	// receivers
	for i := 0; i < receiver; i++ {
		go func(id string) {
			defer wg.Done()
			for {
				// 提前關閉goroutine
				select {
				case <-stopCh:
					return
				default:
				}

				select {
				case <-stopCh:
					return
				case value := <-numCh:
					if value == maxRandomNumber-1 {
						select {
						case toStop <- "receiver#" + id:
						default:
						}
						return
					}

					t.Log(value)
				}
			}
		}(strconv.Itoa(i))
	}

	wg.Wait()
	t.Log("stopped by", stoppedBy)
}

避免重復關閉通道

可以使用 sync.once 語法來避免重復關閉通道:

type MyChannel struct {
	C    chan interface{}
	once sync.Once
}

func NewMyChannel() *MyChannel {
	return &MyChannel{C: make(chan interface{})}
}

func (mc *MyChannel) SafeClose() {
	mc.once.Do(func(){
		close(mc.C)
	})
}

也可以使用 sync.Mutex 語法避免重復關閉通道:

type MyChannel struct {
	C      chan interface{}
	closed bool
	mutex  sync.Mutex
}

func NewMyChannel() *MyChannel {
	return &MyChannel{C: make(chan interface{})}
}

func (mc *MyChannel) SafeClose() {
	mc.mutex.Lock()
	if !mc.closed {
		close(mc.C)
		mc.closed = true
	}
	mc.mutex.Unlock()
}

func (mc *MyChannel) IsClosed() bool {
	mc.mutex.Lock()
	defer mc.mutex.Unlock()
	return mc.closed
}

總結(jié)

如何正確關閉 gotoutine 和 channel 防止內(nèi)存泄漏是一個重要的課題,如果在編碼過程中,遇到了需要打破Channel Closing Principle原則的情況,一定要思考自己的代碼設計是否合理。

到此這篇關于一文教你Golang如何正確關閉通道 的文章就介紹到這了,更多相關go關閉通道 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • golang websocket 服務端的實現(xiàn)

    golang websocket 服務端的實現(xiàn)

    這篇文章主要介紹了golang websocket 服務端的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-09-09
  • golang中的jwt使用教程流程分析

    golang中的jwt使用教程流程分析

    這篇文章主要介紹了golang中的jwt使用教程,接下來我們需要講解一下Claims該結(jié)構體存儲了token字符串的超時時間等信息以及在解析時的Token校驗工作,需要的朋友可以參考下
    2023-05-05
  • Golang 字符串轉(zhuǎn)time類型實現(xiàn)

    Golang 字符串轉(zhuǎn)time類型實現(xiàn)

    本文主要介紹了Golang 字符串轉(zhuǎn)time類型實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03
  • 利用Go語言快速實現(xiàn)一個極簡任務調(diào)度系統(tǒng)

    利用Go語言快速實現(xiàn)一個極簡任務調(diào)度系統(tǒng)

    任務調(diào)度(Task Scheduling)是很多軟件系統(tǒng)中的重要組成部分,字面上的意思是按照一定要求分配運行一些通常時間較長的腳本或程序。本文將利用Go語言快速實現(xiàn)一個極簡任務調(diào)度系統(tǒng),感興趣的可以了解一下
    2022-10-10
  • Golang如何讀取單行超長的文本詳解

    Golang如何讀取單行超長的文本詳解

    這篇文章主要給大家介紹了關于Golang如何讀取單行超長文本的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2021-12-12
  • 在Golang中使用http.FileServer返回靜態(tài)文件的操作

    在Golang中使用http.FileServer返回靜態(tài)文件的操作

    這篇文章主要介紹了在Golang中使用http.FileServer返回靜態(tài)文件的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • go語言go?func(){select{}}()的用法

    go語言go?func(){select{}}()的用法

    本文主要介紹了go語言go?func(){select{}}()的用法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-02-02
  • golang中字符串MD5生成方式總結(jié)

    golang中字符串MD5生成方式總結(jié)

    在本篇文章里小編給大家整理的是一篇關于golang中字符串MD5生成方式總結(jié)內(nèi)容,有興趣的朋友們可以跟著學習參考下。
    2021-07-07
  • go語言實現(xiàn)聊天服務器的示例代碼

    go語言實現(xiàn)聊天服務器的示例代碼

    這篇文章主要介紹了go語言實現(xiàn)聊天服務器的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • Go語言深度拷貝工具deepcopy的使用教程

    Go語言深度拷貝工具deepcopy的使用教程

    今天給大家推薦的工具是deepcopy,一個可以對指針、接口、切片、結(jié)構體、Map都能進行深拷貝的工具,感興趣的小伙伴快跟隨小編一起學習學習
    2022-09-09

最新評論