Golang中为什么使用struct{}{}

Golang中为什么使用struct{}{} 这篇文章使用了以下结构:

done := make(chan struct{}, 2)
done <- struct{}{}

struct{} 是一种惯用法,用于表示其内容无关紧要,但其存在本身很重要吗? 它是最小的类型吗?它比 boolbyte 占用的空间更少吗?它的大小是零吗? 我运行了一个测试,我认为所有问题的答案都是肯定的:

package main

import (
    "fmt"
)

func main() {
    for i := 0; i < 100; i++ {
        a := make([]struct{}, 4000000000)
        fmt.Println(i, len(a))
    }
}

这个程序运行得非常快。 如果我使用 byte 而不是 struct{},程序就会卡住,因为需要寻找内存。

一个大小为 0 的数组(例如 [0]bool)也具有这种零大小的特性。


更多关于Golang中为什么使用struct{}{}的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

我想念Dave Cheney写的Go文章。

更多关于Golang中为什么使用struct{}{}的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你可以在这里阅读一篇关于空结构体的好文章。

https://dave.cheney.net/2014/03/25/the-empty-struct

是的,struct{} 是最小的类型。我喜欢把它想象成 C 语言中的 void,但它实际上是可以实例化的。它是一个结构体,但没有字段,因此不需要分配内存。它对于表示完成或就绪的信号很有用。在实现基于映射的集合类型时也很有用,例如:

setOfIntegers := make(map[int]struct{})

golangprograms.com

Go语言中的结构体,声明和创建结构体数据类型 - golangprograms.com

在本教程中,您将学习:什么是结构体、初始化结构体、嵌套结构体、为结构体创建默认值、比较结构体、理解公有和私有值、区分指针和值引用。

关于集合的观点很好。你可以这样使用它:

	set := make(map[int]struct{})
	set[3] = struct{}{}
	if _, ok := set[3]; !ok {
		t.Fail()
	}

或者像这样:

type Set[T comparable] map[T]struct{}

func (s Set[T]) Put(t T) {
	s[t] = struct{}{}
}

func (s Set[T]) Contains(t T) bool {
	_, ok := s[t]
	return ok
}

func main() {
	set := make(Set[int])
	set.Put(3)
	fmt.Printf("%v %v\n", set.Contains(3), set.Contains(1))
	fmt.Printf("%v %v\n", set[3], set[1])
}

在Go语言中,struct{}确实是一种特殊的零大小类型,常用于信号传递场景。以下是详细解释和示例:

1. 零大小特性

struct{}的大小为0,这在Go语言规范中有明确定义:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    fmt.Println("struct{} size:", unsafe.Sizeof(struct{}{}))  // 0
    fmt.Println("bool size:", unsafe.Sizeof(true))           // 1
    fmt.Println("byte size:", unsafe.Sizeof(byte(0)))        // 1
    
    // 零大小数组
    var arr [1000000]struct{}
    fmt.Println("array size:", unsafe.Sizeof(arr))           // 0
}

2. 通道中的使用

在通道中使用struct{}作为信号是最常见的用法:

package main

import (
    "fmt"
    "time"
)

func worker(done chan struct{}) {
    time.Sleep(1 * time.Second)
    done <- struct{}{}  // 发送完成信号
}

func main() {
    done := make(chan struct{})
    
    go worker(done)
    
    // 等待完成信号
    <-done
    fmt.Println("Worker completed")
}

3. 内存效率

零大小类型在切片和映射中特别高效:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    // 创建大量struct{}元素
    set := make(map[string]struct{})
    for i := 0; i < 1000000; i++ {
        set[fmt.Sprintf("key%d", i)] = struct{}{}
    }
    
    // 内存使用统计
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("Alloc = %v MiB\n", m.Alloc/1024/1024)
}

4. 集合实现

使用map[T]struct{}实现集合:

package main

import "fmt"

type Set map[string]struct{}

func (s Set) Add(key string) {
    s[key] = struct{}{}
}

func (s Set) Contains(key string) bool {
    _, ok := s[key]
    return ok
}

func main() {
    s := make(Set)
    s.Add("apple")
    s.Add("banana")
    
    fmt.Println("Contains apple:", s.Contains("apple"))  // true
    fmt.Println("Contains orange:", s.Contains("orange")) // false
}

5. 与bool的比较

package main

import (
    "fmt"
    "unsafe"
)

func compareMemory() {
    // 使用struct{}的通道
    ch1 := make(chan struct{}, 1000)
    
    // 使用bool的通道
    ch2 := make(chan bool, 1000)
    
    fmt.Printf("chan struct{} size: %d\n", unsafe.Sizeof(ch1))
    fmt.Printf("chan bool size: %d\n", unsafe.Sizeof(ch2))
    
    // 填充通道
    for i := 0; i < 1000; i++ {
        ch1 <- struct{}{}
        ch2 <- true
    }
}

6. 实际应用示例

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

func process(ctx context.Context, wg *sync.WaitGroup, done chan struct{}) {
    defer wg.Done()
    
    select {
    case <-ctx.Done():
        fmt.Println("Cancelled")
    case <-time.After(2 * time.Second):
        fmt.Println("Process completed")
        done <- struct{}{}
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    
    var wg sync.WaitGroup
    done := make(chan struct{})
    
    wg.Add(1)
    go process(ctx, &wg, done)
    
    // 等待处理完成或超时
    select {
    case <-done:
        fmt.Println("Received completion signal")
    case <-ctx.Done():
        fmt.Println("Timeout reached")
    }
    
    wg.Wait()
}

struct{}在Go中确实是最小的零大小类型,主要用于信号传递和内存高效的数据结构实现。它比boolbyte更节省内存,特别是在需要大量实例的场合。

回到顶部