空結構體(struct{})
- 普通理解
在結構體中,可以包裹一系列與對象相關的屬性,但若該對象沒有屬性呢?那它就是一個空結構體。
空結構體,和正常的結構體一樣,可以接收方法函數。
type Lamp struct{}func (l Lamp) On() {println("On")}
func (l Lamp) Off() {println("Off")
}
- 空結構體的妙用
空結構體的表象特征,就是沒有任何屬性,而從更深層次的角度來說,空結構體是一個不占用空間的對象。
使用 unsafe.Sizeof 可以輕易的驗證這個結果
type Lamp struct{}func main() {lamp := Lamp{}fmt.Print(unsafe.Sizeof(lamp))
}
// output: 0
基于這個特性,在一些特殊的場合之下,可以用做占位符使用,合理的使用空結構體,會減小程序的內存占用空間。
比如在使用信道(channel)控制并發時,我們只是需要一個信號,但并不需要傳遞值,這個時候,也可以使用 struct{} 代替。
func main() {ch := make(chan struct{}, 1)go func() {<-ch// do something}()ch <- struct{}{}// ...
}
在 Go 語言中,使用空結構體(struct{}
)作為通道(chan
)的元素類型是一種常見的優化手段。這種做法主要出于以下幾個原因:
-
節省內存
空結構體struct{}
在 Go 中不占用任何內存空間(大小為 0 字節)。因此,當你需要一個通道來傳遞信號或同步協程時,使用空結構體可以避免不必要的內存開銷。 -
信號傳遞
在某些場景下,你并不需要通過通道傳遞具體的數據,而只是需要一個簡單的信號機制來通知其他協程某個事件已經發生。例如,用于關閉多個工作協程、通知某個操作完成等。此時,空結構體作為通道的元素類型非常合適。 -
提高性能
由于空結構體不占用內存,發送和接收空結構體的操作通常比發送和接收復雜數據類型的通道更快。雖然這種差異在大多數情況下是微不足道的,但在高并發或高性能要求的場景下,這些細微的優化可能會產生顯著的影響。
.關閉多個工作協程
package mainimport ("fmt""time"
)func worker(id int, done chan struct{}) {for {select {case <-done:fmt.Printf("Worker %d shutting down\n", id)returndefault:fmt.Printf("Worker %d working\n", id)time.Sleep(500 * time.Millisecond)}}
}func main() {done := make(chan struct{})numWorkers := 3// 啟動多個工作協程for i := 1; i <= numWorkers; i++ {go worker(i, done)}// 模擬一些工作time.Sleep(2 * time.Second)// 發送關閉信號close(done)// 等待一段時間以確保所有工作協程都已退出time.Sleep(1 * time.Second)
}
在這個例子中,done
通道被用來通知所有工作協程停止工作。我們不需要通過通道傳遞任何實際的數據,只需要一個信號即可。
.同步操作完成
package mainimport ("fmt""sync"
)func task(id int, wg *sync.WaitGroup, done chan struct{}) {defer wg.Done()fmt.Printf("Task %d completed\n", id)done <- struct{}{} // 發送一個空結構體表示任務完成
}func main() {var wg sync.WaitGroupdone := make(chan struct{}, 3) // 緩沖區大小為任務數量for i := 1; i <= 3; i++ {wg.Add(1)go task(i, &wg, done)}// 等待所有任務完成go func() {wg.Wait()close(done)}()// 接收所有完成信號for range done {fmt.Println("Received completion signal")}fmt.Println("All tasks completed")
}
在這個例子中,每個任務完成后都會向 done
通道發送一個空結構體,表示任務已完成。主協程通過讀取 done
通道中的信號來確認所有任務是否已完成。