在 Go 语言中,线程安全与非线程安全的数据类型主要取决于其设计特性和使用场景。以下是详细的分类和说明:

一、线程安全的数据类型

线程安全的数据类型在并发环境中无需额外同步机制即可安全使用,通常满足以下条件:

  • 不可变性(如基本类型)。
  • 内部同步机制(如 channelsync.Map)。
  • 原子操作支持(如 atomic 包中的类型)。

1. 基本类型(不可变)

  • 线程安全原因:这些类型在赋值时可以通过一条机器指令完成(通常不超过 64 位),因此在多协程中读写是安全的。
    • intuint
    • float32float64
    • complex64complex128
    • byterune
    • uintptr
    • bool
    • 注意:在 32 位系统中,int64uint64 的赋值可能需要两条指令,因此不线程安全。

2. 同步原语

  • sync.Mutexsync.RWMutex
    • 用于显式加锁,保护共享资源的并发访问。
  • sync.Map
    • Go 1.9 引入的并发安全 map,适用于高频读写的场景。
  • channel
    • 内部实现锁机制,支持协程间安全通信,发送和接收操作是原子的。
    • 无缓冲 channel:发送和接收会阻塞,直到双方就绪。
    • 有缓冲 channel:缓冲区未满/空时非阻塞,但仍保证线程安全。

3. 原子操作(sync/atomic

  • 线程安全的原子类型
    • atomic.Int32atomic.Int64
    • atomic.Uint32atomic.Uint64
    • atomic.Value
  • 原子操作函数
    • atomic.AddInt32atomic.CompareAndSwapInt32 等。
    • 适用于对基本类型(如整数、指针)的并发修改。

4. 其他线程安全类型

  • time.Timertime.Ticker
    • 用于定时任务,内部已处理并发问题。
  • sync.WaitGroup
    • 用于等待一组协程完成,内部通过计数器同步。

二、非线程安全的数据类型

非线程安全的数据类型在并发访问时可能引发数据竞争,需要外部同步机制(如锁、原子操作)保护。

1. 复合类型(可变)

  • map
    • 并发读写会导致数据损坏(Go 1.12 后会触发运行时 panic)。
    • 解决方案
      • 使用 sync.RWMutex 加锁。
      • 使用 sync.Map 替代。
  • slice
    • 并发追加或修改可能导致数据竞争。
    • 解决方案:使用互斥锁(sync.Mutex)保护。
  • array
    • 固定大小的数组,修改元素时需加锁。
  • struct
    • 如果包含可变字段(如 mapslice),需通过锁保护。
  • interface{}
    • 接口类型本身不可变,但底层存储的值可能可变。
  • pointer
    • 指针本身是线程安全的,但指向的数据可能需要保护。

2. I/O 相关类型

  • os.File
    • 文件读写操作(如 ReadWrite)需确保单协程访问。
  • 网络连接(net.Conn
    • 需由单协程管理读写,避免并发冲突。

3. 其他非线程安全类型

  • string
    • 字符串本身不可变,但赋值操作(如拼接)可能涉及内存分配,需注意并发场景。
  • complex128complex64
    • 在 32 位系统中可能非线程安全。
  • 自定义结构体
    • 若包含可变字段(如 mapslice),需显式加锁。

三、线程安全的实现方式

  1. 互斥锁(sync.Mutex / sync.RWMutex

    • 适用于保护任意共享资源。
    • 示例:
      var mu sync.Mutex
      var data map[string]int
      
      func safeSet(key string, value int) {
          mu.Lock()
          defer mu.Unlock()
          data[key] = value
      }
      
  2. 原子操作(sync/atomic

    • 适用于对基本类型(如 int64)的并发修改。
    • 示例:
      var count int64
      atomic.AddInt64(&count, 1)
      
  3. sync.Map

    • 专为并发场景设计的 map,无需额外锁。
    • 示例:
      var m sync.Map
      m.Store("key", 1)
      value, _ := m.Load("key")
      
  4. channel

    • 通过通道传递数据,避免共享内存。
    • 示例:
      ch := make(chan int)
      go func() {
          ch <- 42
      }()
      fmt.Println(<-ch)
      

四、总结

线程安全类型 非线程安全类型
intfloat32bool 等基本类型 mapslicearray
sync.Mutexsync.RWMutex struct(含可变字段时)
sync.Mapatomic 包类型 interface{}pointer
channel os.Filenet.Conn

关键原则

  • 不可变即安全:基本类型和只读数据天然线程安全。
  • 共享即需同步:所有共享资源(如 mapslice)需通过锁或原子操作保护。
  • 优先使用并发安全类型:如 sync.Mapchannel,减少手动同步的复杂性。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐