Golang教程四(协程,channel,线程安全,syncMap)

目录

一、Goroutine和channel

Goroutine

Channel

发送和接收操作

缓冲 Channel

关闭 Channel

使用 Channel 进行同步

Select语句

协程超时处理 

方法一:使用context.Context

 方法二:使用time.Timer/time.Ticker

 二、线程安全与sync.Map

线程安全

 sync.RWMutex(互斥锁)

sync.RWMutex (读写互斥锁)

sync.WaitGroup

sync.Once

sync.Cond

sync.map

sync.atomic


一、Goroutine和channel

Goroutine

Goroutine是Go语言中实现并发的一种轻量级线程,由Go运行时管理。Goroutines与操作系统线程相比,具有更低的创建和切换开销,使得Go语言能够轻松地实现高并发编程。

创建 Goroutine: 在Go语言中,使用关键字 go 与函数调用结合即可创建一个Goroutine

Goroutine间的通信: Go语言提倡通过 channels(通道)进行Goroutine间的通信和同步。Channels是类型化的管道,可以发送和接收指定类型的值。通过 channels,Goroutines可以安全地共享和交换数据,避免数据竞争和竞态条件(达到线程安全的目的)。

package mainimport ("fmt""time"
)func main() {ch := make(chan int)go func() {time.Sleep(1 * time.Second)ch <- 42 // 发送一个整数到channel ch}()fmt.Println("准备输出:")result := <-ch      // 从channel ch 接收一个整数fmt.Println(result) // 输出:42
}

Channel

在Go语言中,通道(Channel)是一种核心的并发原语,用于在Goroutines之间进行通信和同步。通道是类型化的,只能发送和接收指定类型的值。

通过 Channel,可以有效地协调并发任务,确保数据正确流动,构建复杂的并发程序。理解和熟练使用 Channel 是编写高效、安全的 Go 语言代码的关键。

发送和接收操作

向 Channel 发送值使用 <- 运算符左侧,接收值使用 <- 运算符右侧:

ch := make(chan int)go func() {ch <- 42 // 向 Channel ch 发送整数 42
}()value := <-ch // 从 Channel ch 接收一个整数,并赋值给变量 value
fmt.Println(value) // 输出:42

缓冲 Channel

通过在 make 函数中指定第二个参数,可以创建带缓冲的 Channel。缓冲 Channel 允许在没有接收者时暂存一定数量的数据,直到被接收。缓冲大小为指定的整数值:

bufferedCh := make(chan int, 5) // 创建一个缓冲大小为 5 的整数 Channel

关闭 Channel

使用 close 函数关闭 Channel,表示不会再有新的值发送到该 Channel。关闭 Channel 后,接收操作会继续接收剩余的值,直到 Channel 空。尝试向已关闭的 Channel 发送值会导致 panic。接收操作可以从已关闭的 Channel 接收值,直到 Channel 空。若 Channel 已关闭且为空,接收操作会立即返回零值,并且 ok 参数(如 value, ok := <-ch)设为 false,表明 Channel 已关闭且无值可接收。

close(ch) // 关闭 Channel ch

使用 Channel 进行同步

Channel 除了用于数据传输,还能作为同步机制。通过发送和接收操作的阻塞特性,可以实现 Goroutine 间的同步和协作:

doneCh := make(chan struct{})
go func() {// 执行任务...close(doneCh) // 任务完成时关闭 doneCh
}()<-doneCh // 阻塞,等待 doneCh 被关闭,即任务完成

Select语句

select 语句用于监听多个 Channel 的发送或接收操作,当任意一个操作可以进行时,select 语句会选择一个执行。select 语句支持 default 子句,当所有 Channel 都不可操作时执行。

ch1 := make(chan int)
ch2 := make(chan string)select {
case i := <-ch1:fmt.Println("Received from ch1:", i)
case s := <-ch2:fmt.Println("Received from ch2:", s)
default:fmt.Println("Both channels are empty")
}

协程超时处理 

处理协程(Goroutine)超时通常有两种常用方法:使用context.Context和使用time.Timer/time.Ticker。以下是这两种方法的详细介绍

方法一:使用context.Context

context.Context 是 Go 语言中用于在多个 Goroutine 之间传播截止时间、取消信号以及携带请求范围内的上下文信息的标准方式。要实现协程超时,可以创建一个带超时时间的context.Context,并将它传递给协程执行的函数。当超时时间到达时,Context 会触发取消信号,通知协程停止执行。

package mainimport ("context""fmt""time"
)func doWork(ctx context.Context) error {// 在循环、IO操作等处定期检查 ctx.Done(),一旦返回非nil,说明超时或被取消for {select {case <-ctx.Done():return ctx.Err() // 返回一个表示超时或取消的错误default:// 正常执行任务}}
}func main() {ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 创建一个5秒后超时的Contextdefer cancel()                                                          // 及时取消Context,释放资源go func() {if err := doWork(ctx); err != nil {fmt.Println("doWork failed:", err)}}()// 其他操作for i := 0; i < 6; i++ {time.Sleep(1 * time.Second)fmt.Printf("doWork is running... %d \n", i)}}

 方法二:使用time.Timer/time.Ticker

time.Timer 和 time.Ticker 提供了基于时间的定时器功能。Timer 用于一次性触发,而 Ticker 用于周期性触发。可以创建一个 Timer,并在其到期时取消正在执行的协程。

package mainimport ("fmt""sync""time"
)func doWork(stopCh chan struct{}) error {// 在循环、IO操作等处定期检查 stopCh,一旦收到信号,说明需要停止for {select {case <-stopCh:return nil // 返回表示被停止的错误default:// 正常执行任务}}
}func main() {stopCh := make(chan struct{})var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()if err := doWork(stopCh); err != nil {fmt.Println("doWork failed:", err)}}()timer := time.NewTimer(5 * time.Second) // 创建一个5秒后到期的Timergo func() {<-timer.C     // 等待Timer到期close(stopCh) // 发送停止信号}()wg.Wait() // 等待doWork协程完成
}

 二、线程安全与sync.Map

线程安全

Golang 中的线程安全是指编写并发程序时,确保即使在多个并发执行的 Goroutines(Go 语言中的轻量级线程)同时访问和修改共享数据时,也能保持程序的正确性、一致性和完整性,避免数据竞争、竞态条件等并发问题的发生。

以下是关于 Golang 线程安全的详细说明:

  1. 共享状态与数据竞争: 在并发环境中,当多个 Goroutines 访问和/或修改相同的内存区域(如全局变量、共享数据结构等)时,就存在共享状态。如果没有采取适当的同步措施,不同的 Goroutine 可能会交错执行,导致数据的非预期更新,这就是所谓的数据竞争。例如,多个 Goroutine 同时对一个计数器进行递增操作可能会导致最终计数值小于实际递增次数。

  2. 同步原语与机制: Go 语言提供了丰富的同步原语和机制来实现线程安全:

    • 互斥锁 (sync.Mutex 和 sync.RWMutex): 互斥锁用于保护临界区,确保同一时刻只有一个 Goroutine 能访问受保护的资源。sync.Mutex 提供独占锁,当一个 Goroutine 获取锁后,其他尝试获取锁的 Goroutines 将被阻塞,直到锁被释放。sync.RWMutex 是读写锁,支持多个读取者并发访问,但在写入时会阻塞所有读取者和其他写入者。

    • 通道 (chan): 通道是 Go 语言的核心并发原语,用于在 Goroutines 之间传递数据。通道的发送和接收操作是同步的,即发送操作会阻塞直到有接收者接收数据,反之亦然。通过使用通道,可以实现 Goroutines 之间的同步和通信,避免直接共享数据带来的线程安全问题。

    • 其他同步工具: sync 包还提供了其他同步工具,如:

      • sync.WaitGroup:用于等待一组 Goroutines 完成其任务。
      • sync.Once:确保某个初始化动作只被执行一次,即使在并发环境中。
      • sync.Cond:允许 Goroutines 在满足特定条件时进行通知和等待。
    • 原子操作 (sync/atomic): sync/atomic 包提供了原子级别的整数和指针操作,这些操作在硬件级别上保证了其不可中断性,即使在并发环境中也能确保数据的一致性,无需使用锁。例如,可以使用 atomic.AddInt64 来实现无锁的整数递增。

  3. 线程安全的数据结构: Go 标准库中包含了一些线程安全的数据结构,如 sync.Mapsync.Map 是一个并发安全的映射(map),内部已经实现了适当的锁机制,使得多个 Goroutines 可以安全地并发读写。

实现 Golang 线程安全的关键在于正确地使用上述同步原语来管理对共享数据的访问。这通常包括确定哪些数据需要同步、选择合适的同步机制(如互斥锁、通道、原子操作等)、遵循最小化同步区域的原则以提高并发性能,并避免死锁等并发问题。

总之,Golang 中的线程安全是通过使用语言提供的同步原语和机制,确保在并发环境下对共享数据的操作是有序且一致的,从而防止数据竞争、竞态条件等并发问题,保证程序的正确运行。

 sync.RWMutex(互斥锁

关键特性

  1. 互斥性sync.Mutex 保证了同一时间只有一个 Goroutine 能持有锁,即只有一个 Goroutine 能进入临界区。其他尝试获取锁的 Goroutines 将被阻塞,直到锁被持有锁的 Goroutine 释放。

  2. 重入:Go 的互斥锁不支持重入,即已经持有锁的 Goroutine 不能再次获取同一把锁。如果尝试这样做,程序将引发 panic。

  3. 锁定与解锁:互斥锁有两个主要方法:

    • Lock():获取锁。如果锁当前未被持有,调用者获得锁并继续执行;如果锁已被持有,调用者将被阻塞,直到锁被释放。
    • Unlock():释放锁。只有持有锁的 Goroutine 才能成功释放锁。释放后,如果有其他等待的 Goroutine,其中一个将被唤醒并获得锁。
  4. 零值有效性sync.Mutex 的零值就是一个有效的、未锁定的互斥锁,无需显式初始化即可使用。

package mainimport ("fmt""sync"
)var (value intwait  sync.WaitGroupmu    sync.Mutex // 保护 value 字段的互斥锁
)func increment() {defer wait.Done()mu.Lock() // 获取锁value++mu.Unlock() // 释放锁
}// 如果不加锁,结果是随机的
// 根本原因是CPU的调度方法为抢占式执行,随机调度
func main() {for i := 0; i < 1000; i++ {wait.Add(1)go increment()}wait.Wait()fmt.Println(value)
}

注意事项

  1. 不要忘记解锁:长时间持有锁会导致其他 Goroutines 长期阻塞,降低程序的并发性能。确保及时释放不再需要的锁。

  2. 不要重复锁定:一个 Goroutine 不能对已持有的互斥锁再次调用 Lock()。重复锁定会导致 panic。

  3. 避免解锁未锁定的锁:调用 Unlock() 之前必须确保锁已被持有。对未锁定的锁进行解锁同样会引发 panic。

  4. 合理组织临界区:尽量缩小临界区的范围,仅包含真正需要同步的代码,以减少锁的争用和提高并发效率。

sync.RWMutex (读写互斥锁)

sync.RWMutex 是 Go 语言标准库 sync 包中提供的读写互斥锁类型,它扩展了基本互斥锁(sync.Mutex)的功能,专为处理读多写少的并发场景。sync.RWMutex 允许任意数量的 Goroutines 同时进行读操作,但在进行写操作时,会阻止所有读取者和写入者,以保证数据的完整性。

关键特性与方法

  1. 读锁(共享锁):多个 Goroutine 可以同时持有读锁,这意味着它们可以并发地进行读操作。读锁由 RLock() 方法获取,由 RUnlock() 方法释放。

  2. 写锁(排他锁):任何时候只能有一个 Goroutine 持有写锁。当有 Goroutine 持有写锁时,其他所有试图获取读锁或写锁的 Goroutines 都将被阻塞。写锁由 Lock() 方法获取,由 Unlock() 方法释放。

  3. 零值有效性:同 sync.Mutexsync.RWMutex 的零值也是一个有效的、未锁定的读写互斥锁。

package mainimport "sync"type SharedCounter struct {count intmu    sync.RWMutex
}func (c *SharedCounter) ReadCount() int {c.mu.RLock()         // 获取读锁defer c.mu.RUnlock() // 释放读锁return c.count
}func (c *SharedCounter) Increment() {c.mu.Lock()         // 获取写锁defer c.mu.Unlock() // 释放写锁c.count++
}//在这个例子中:
//ReadCount 方法使用 RLock() 获取读锁,允许多个 Goroutine 并发读取 count 值,提高了读取操作的并发性能。
//Increment 方法使用 Lock() 获取写锁,确保在递增 count 的过程中,没有其他 Goroutine(无论是读还是写)能访问 count,保证了数据更新的原子性。

 注意事项

  1. 读写平衡:虽然 sync.RWMutex 支持并发读,但如果写操作过于频繁或持有写锁的时间过长,可能会导致大量读取 Goroutines 长时间阻塞,降低系统的整体并发性能。因此,应合理设计数据结构和算法,尽量减少写操作的频率和持续时间。

  2. 避免死锁:与 sync.Mutex 类似,确保每个 Lock()RLock() 操作都有对应的 Unlock()RUnlock(),且在正确的代码路径上执行。使用 defer 可以简化这一过程。

  3. 不要忘记解锁:长时间持有锁(无论是读锁还是写锁)都会影响其他 Goroutines 的执行。确保及时释放不再需要的锁。

  4. 不要重复锁定:一个 Goroutine 不能对已持有的锁(无论读锁还是写锁)再次调用 Lock() 或 RLock()。重复锁定会导致 panic。

  5. 避免解锁未锁定的锁:调用 Unlock() 或 RUnlock() 之前必须确保相应的锁已被持有。对未锁定的锁进行解锁同样会引发 panic。

总结来说,sync.RWMutex 是 Go 语言中针对读多写少场景优化的同步原语,通过区分读锁和写锁,允许并发读取以提高系统性能,同时在写操作时保证数据一致性。在设计并发程序时,根据数据的访问模式合理选择和使用 sync.Mutex 或 sync.RWMutex,有助于构建高效且线程安全的并发代码。

sync.WaitGroup

sync.WaitGroup 是 Go 语言标准库 sync 包中提供的一个类型,用于同步一组 Goroutines 的执行,确保所有 Goroutines 完成其任务后,主线程或其他相关 Goroutines 才能继续执行后续操作。它常用于并行计算、批处理任务、网络请求聚合等场景。

sync.WaitGroup 主要包含以下方法:

  1. Add(delta int)

    将 WaitGroup 的计数器增加 delta。通常在启动 Goroutine 前调用,传入 delta=1 表示增加一个待完成的任务。
  2. Done()

    将 WaitGroup 的计数器减一。当一个 Goroutine 完成其任务时,应调用此方法。相当于 Add(-1)
  3. Wait()

    阻塞当前 Goroutine,直到 WaitGroup 的计数器变为零,即所有待完成的任务都已经调用了 Done()。这意味着所有关联的 Goroutines 已经完成其任务。
package mainimport ("fmt""sync""time"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // Goroutine 完成时,通过 defer 语句确保调用 Done()fmt.Printf("Worker %d started\n", id)time.Sleep(time.Second * time.Duration(id))fmt.Printf("Worker %d finished\n", id)
}func main() {var wg sync.WaitGroupfor i := 1; i <= 3; i++ {wg.Add(1) // 启动一个 Goroutine 前,增加 WaitGroup 计数器go worker(i, &wg)}fmt.Println("Waiting for workers to finish...")wg.Wait() // 阻塞主线程,直到所有 worker Goroutines 完成fmt.Println("All workers have finished.")
}

在这个例子中,我们使用 sync.WaitGroup 同步三个 worker Goroutines 的执行。在启动每个 Goroutine 之前,通过 wg.Add(1) 增加 WaitGroup 的计数器。每个 worker Goroutine 在完成任务后调用 wg.Done()。主线程在 wg.Wait() 处阻塞,直到所有 worker Goroutines 都调用了 Done(),即所有任务都已完成,然后继续执行后续代码。

总结来说,sync.WaitGroup 是 Go 语言中用于同步一组 Goroutines 执行的工具,它通过计数器跟踪待完成的任务数量,确保所有 Goroutines 完成其任务后,主线程或其他相关 Goroutines 才能继续执行。通过使用 sync.WaitGroup.Add()sync.WaitGroup.Done() 和 sync.WaitGroup.Wait(),可以方便地管理一组并发任务的执行,确保任务的正确完成和程序的正确同步。

sync.Once

sync.Once 是 Go 语言标准库 sync 包中提供的一个类型,用于确保某个初始化动作只被执行一次,即使在并发环境中。它常用于初始化全局变量、连接数据库、加载配置文件等只需进行一次的初始化操作。使用 sync.Once 可以确保在多线程或 Goroutine 并发访问时,这些操作不会被重复执行,从而避免资源浪费、数据不一致等问题。

sync.Once 主要包含两个方法:

  1. Do(f func())

    • 参数 f 是一个无参数、无返回值的函数,代表需要只执行一次的初始化动作。
    • 当首次调用 Do(f) 时,f 会被执行。之后对 Do(f) 的任何调用都不会再执行 f
    • Do(f) 方法是线程安全的,即使在多个 Goroutine 并发调用的情况下,也能确保 f 只被执行一次。
  2. Done()

    • 返回一个只读的布尔值,表示初始化动作是否已经完成。
    • 通常情况下,你不需要直接调用 Done(),因为它主要用于测试和调试。
package mainimport ("fmt""sync"
)var once sync.Once
var message stringfunc initMessage() {message = "Hello, World!"fmt.Println("Initialization performed.")
}func GetMessage() string {once.Do(initMessage)return message
}func main() {go func() {fmt.Println(GetMessage())}()go func() {fmt.Println(GetMessage())}()// 程序在此处阻塞,等待 Goroutines 完成var input stringfmt.Scanln(&input)
}

sync.Cond

sync.Cond 是 Go 语言标准库 sync 包中提供的一个条件变量类型,用于协调多个 Goroutines 的执行。条件变量允许 Goroutines 在满足特定条件时进行通知和等待,常用于解决生产者-消费者模型、工作池、资源池等场景中的同步问题。

sync.Cond 主要包含以下方法:

  1. NewCond(l Locker)

    • 创建一个新的 sync.Cond 对象,需要传入一个实现了 sync.Locker 接口(如 sync.Mutex 或 sync.RWMutex)的对象作为底层锁。所有与 sync.Cond 相关的操作都需要在锁的保护下进行。
  2. Wait()

    • 调用 Wait() 的 Goroutine 会释放底层锁,并进入等待状态,直到收到信号或广播通知。收到通知后,Goroutine 会重新获取底层锁并继续执行。
  3. Signal()

    • 发送一个信号给等待队列中的一个 Goroutine。被唤醒的 Goroutine 将重新获取底层锁并继续执行。如果此时没有 Goroutine 在等待,信号将被忽略。
  4. Broadcast()

    • 向等待队列中的所有 Goroutine 发送广播通知。所有被唤醒的 Goroutine 将重新获取底层锁并继续执行。

使用场景

sync.Cond 经常用在多个 Goroutine 等待,一个 Goroutine 通知(事件发生)的场景。如果是一个通知,一个等待,使用互斥锁或 channel 就能搞定了。

我们想象一个非常简单的场景:

有一个协程在异步地接收数据,剩下的多个协程必须等待这个协程接收完数据,才能读取到正确的数据。在这种情况下,如果单纯使用 chan 或互斥锁,那么只能有一个协程可以等待,并读取到数据,没办法通知其他的协程也读取数据。

这个时候,就需要有个全局的变量来标志第一个协程数据是否接受完毕,剩下的协程,反复检查该变量的值,直到满足要求。或者创建多个 channel,每个协程阻塞在一个 channel 上,由接收数据的协程在数据接收完毕后,逐个通知。总之,需要额外的复杂度来完成这件事。

Go 语言在标准库 sync 中内置一个 sync.Cond 用来解决这类问题。

 原理参考:Golang sync.Cond 简介与用法-CSDN博客

package mainimport ("fmt""sync""time"
)var done = falsefunc read(name string, c *sync.Cond) {c.L.Lock()for !done {c.Wait()}fmt.Println(name, "starts reading")c.L.Unlock()
}func write(name string, c *sync.Cond) {fmt.Println(name, "starts writing")time.Sleep(3 * time.Second)done = truefmt.Println(name, "wakes all")c.Broadcast()
}func main() {cond := sync.NewCond(&sync.Mutex{})go read("reader1", cond)go read("reader2", cond)go read("reader3", cond)write("writer", cond)time.Sleep(time.Second * 10)
}

sync.map

sync.Map 是 Go 语言标准库 sync 包中提供的一个并发安全的映射(map)类型,专为多线程环境设计,允许多个 Goroutines 并发地读取和写入数据,而无需手动进行同步。sync.Map 内部实现了适当的锁机制和数据分段技术,能够在保证线程安全的同时,提供比使用互斥锁(如 sync.Mutex)保护普通映射(map)更好的性能。

sync.Map 主要包含以下方法:

  1. Load(key interface{}) (value interface{}, ok bool)

    从映射中查找给定的 key,返回对应的 value 和一个布尔值 ok,表示查找是否成功。如果 key 不存在,value 为对应类型的零值,ok 为 false。读取操作对并发友好,即使在写入操作正在进行时也可以安全地进行。
  2. Store(key, value interface{})

    将给定的 key-value 对存储到映射中。如果 key 已存在,则更新其对应的 value。写入操作可能会阻塞其他写入操作,但不会阻塞读取操作。
  3. Delete(key interface{})

    从映射中删除给定的 key 及其对应的 value。如果 key 不存在,此操作无任何效果。删除操作可能会阻塞其他写入操作,但不会阻塞读取操作。
  4. Range(f func(key, value interface{}) bool)

    遍历映射中的所有 key-value 对。f 是一个回调函数,对每个 key-value 对调用一次。如果 f 返回 false,则停止遍历。遍历操作在遍历期间会锁定整个映射,阻止其他写入操作,但允许并发读取。
  5. Len() int

    返回映射中元素的数量。由于计数可能在并发操作下发生变化,这个值只能视为近似值。
package mainimport ("fmt""sync"
)func main() {var m sync.Mapm.Store("key1", "value1")m.Store("key2", "value2")// 读取操作value, ok := m.Load("key1")if ok {fmt.Println("Value for key1:", value.(string)) // 输出:Value for key1: value1}// 删除操作m.Delete("key2")// 遍历操作m.Range(func(key, value interface{}) bool {fmt.Println("Key:", key, "Value:", value)return true // 继续遍历})// 更新操作m.Store("key1", "new_value1")// 读取新值value, ok = m.Load("key1")if ok {fmt.Println("Updated value for key1:", value.(string)) // 输出:Updated value for key1: new_value1}
}

在这个例子中,我们展示了 sync.Map 的基本使用方法,包括存储、读取、删除、遍历和更新操作。通过使用 sync.Map,可以在并发环境中安全地操作映射数据,无需担心数据竞争和竞态条件。

总结来说,sync.Map 是 Go 语言中用于实现并发安全映射的类型,它内部实现了高效的锁机制和数据分段技术,使得多个 Goroutines 可以安全地并发读取和写入数据。通过使用 sync.Map.Load()sync.Map.Store()sync.Map.Delete()sync.Map.Range() 等方法,可以轻松地在并发环境中管理映射数据,确保程序的正确性和数据一致性。

sync.atomic

sync/atomic 是 Go 语言标准库中的一个包,提供了对整数和指针的原子操作支持。原子操作在硬件级别上保证了操作的不可分割性,即在操作完成之前,其他并发的 Goroutines 无法观察到操作的中间状态。使用 sync/atomic 包中的函数可以确保在并发环境中对整数或指针的读取、修改等操作是原子的,从而避免数据竞争和竞态条件,保障程序的线程安全性。

sync/atomic 包主要包含以下类型和函数:

原子整数类型

  • int32
  • uint32
  • int64
  • uint64
  • uintptr

原子操作函数

  • 原子读取LoadUint32(addr *uint32) (val uint32)LoadInt64(addr *int64) (val int64) 等,用于原子地读取整数或指针的值。
  • 原子存储StoreUint32(addr *uint32, val uint32)StoreInt64(addr *int64, val int64) 等,用于原子地将整数或指针的值设置为给定值。
  • 原子交换SwapUint32(addr *uint32, new uint32) (old uint32)SwapInt64(addr *int64, new int64) (old int64) 等,用于原子地将整数或指针的值替换为给定的新值,并返回旧值。
  • 原子加法AddUint32(addr *uint32, delta uint32) (new uint32)AddInt64(addr *int64, delta int64) (new int64) 等,用于原子地将整数或指针的值增加指定的增量,并返回新的值。
  • 原子比较并交换(CAS):CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) 等,如果当前值等于 old,则将整数或指针的值设置为 new,并返回 true;否则不改变值,返回 false
package mainimport ("fmt""sync/atomic""time"
)func main() {var counter uint32 = 0go func() {for i := 0; i < 10000; i++ {atomic.AddUint32(&counter, 1) // 原子递增 counter}}()go func() {for i := 0; i < 10000; i++ {atomic.AddUint32(&counter, 1) // 原子递增 counter}}()time.Sleep(5 * time.Second) // 等待 Goroutines 完成finalCount := atomic.LoadUint32(&counter) // 原子读取 counter 的最终值fmt.Println("Final count:", finalCount)   // 输出:Final count: 20000
}

总结来说,sync/atomic 包提供了对整数和指针的原子操作支持,使得在并发环境中对这些数据类型的读取、修改等操作能够保持原子性,避免数据竞争和竞态条件,确保程序的线程安全性。通过使用 sync/atomic 包中的函数,如 Load*Store*Swap*Add*CompareAndSwap* 等,可以在 Go 语言中有效地实现无锁的并发数据结构和算法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/808177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue3大事件项目1 登录注册验证

创建项目 引入 element-ui 组件库 登录&#xff1a;注册样式准备之后&#xff0c;配置校验规则&#xff08;4个条件&#xff1a;一数据、二规则&#xff09; 1. 校验相关 (1) 给当前表单绑上整个的数据对象&#xff1a;el-form > :model"ruleForm" 绑…

二叉树总结

递归返回值 1、如果需要搜索整棵二叉树且不用处理递归返回值&#xff0c;递归函数就不要返回值。 2、如果需要搜索整棵二叉树且需要处理递归返回值&#xff0c;递归函数就需要返回值。 3、如果要搜索其中一条符合条件的路径&#xff0c;那么递归一定需要返回值&#xff0c;…

Notepad++软件安装及配置说明

Notepad是 Windows操作系统下的一套文本编辑器&#xff0c;有完整的中文化接口及支持多国语言编写的功能。 Notepad功能比 Windows自带记事本强大&#xff0c;除了可以用来制作一般的纯文字说明文件&#xff0c;也十分适合编写计算机程序代码。Notepad不但可以显示行号&#xf…

1572. 【基础赛】涂色(paint)

1572. 【基础赛】涂色&#xff08;paint&#xff09; (Input: paint.in, Output: paint.out) 时间限制: 2 s 空间限制: 256 MB 具体限制 题目描述 Introl获得了一个N行的杨辉三角&#xff0c;他将每行中值为奇数的位置涂为了黑色。 Chihiro将提出M次询问&#xff0c;在第L…

Day2 字符串哈希KMP

字符串哈希 KMP 基本 字符串哈希 理论 将一个字符串转成一个数字&#xff0c;可以快速比较两个字符串是否相同等。要求为&#xff1a;相同字符串哈希值相同&#xff0c;不同字符串哈希值尽量不相同。 映射方法通常采用多项式哈希方法&#xff0c;很像进制转换。假设字符串为…

Tomcat服务

Tomcat服务 安装 安装java环境 yum install -y java-1.8.0-openjdk java -version # 看是否成功安装Tomcat&#xff0c;这里以apache-tomcat-8.0.30.tar.gz为例 tar -xvf apache-tomcat-8.0.30.tar.gz -C /usr/local/ mv /usr/local/apache-tomcat-8.0.30 /usr/local/tomc…

萝卜大杂烩 | 10 个杀手级的 Python 自动化脚本

本文来源公众号“萝卜大杂烩”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;10 个杀手级的 Python 自动化脚本 重复性任务总是耗时且无聊&#xff0c;想一想你想要一张一张地裁剪 100 张照片或 Fetch API、纠正拼写和语法等工作…

ELK日志

​​​​​​​

HJ53 杨辉三角的变形(基础数学,生成数组不行,会越界,使用规律)

第一种方法&#xff1a; 生成杨辉三角的方法不行&#xff0c;会出现越界&#xff0c; 数组从[0][0]开始&#xff0c;i行j列 只看列 每一行的最右侧坐标为2*i,下坐标为 0&#xff0c; 0&#xff0c;1&#xff0c;2 0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4 … …

MongoDB聚合运算符:$not

文章目录 语法使用举例 $not聚合运算符用于将指定布尔表达式的值取反&#xff0c;比如&#xff0c;表达式的值为 true&#xff0c; $not返回 false&#xff1b;表达式的值为 false&#xff0c; $not则返回 true。 语法 { $not: [ <expression> ] }使用 除false外…

革命性突破:Stability AI发布全新12B参数Stable LM 2模型,颠覆AI界!

Stability AI已推出其Stable LM 2语言模型系列的最新成员&#xff1a;一个120亿参数的基础模型和一个经过指令调优的变体。这些模型在七种语言上训练&#xff0c;包括英语、西班牙语、德语、意大利语、法语、葡萄牙语和荷兰语&#xff0c;训练数据达到了令人印象深刻的两万亿个…

Linux:动态库加载、编址

目录 一、库的概念 二、动静态库的加载 2.1绝对编址与相对编址 2.1一般程序的加载 三、动态库的加载 一、库的概念 库默认就是一个磁盘级文件&#xff0c;所以在执行代码时&#xff0c;库和可执行程序都会被加载到内存中&#xff0c;从原理上&#xff0c;库函数的调用依旧…

结构型模式--1.适配器模式【托尼托尼·乔巴】

1. 翻译家 在海贼王中&#xff0c;托尼托尼乔巴&#xff08;Tony Tony Chopper&#xff09;是草帽海贼团的船医&#xff0c;它本来是一头驯鹿&#xff0c;但是误食了动物系人人果实之后可以变成人的形态。 乔巴吃了恶魔果实之后的战斗力暂且抛开不谈&#xff0c;说说它掌握的第…

[C++][算法基础]树的重心(树图DFS)

给定一颗树&#xff0c;树中包含 n 个结点&#xff08;编号 1∼n&#xff09;和 n−1 条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#xff0c;剩余各个连通块中点数的最大值。 重心定义&#xff1a;重心是指树中的一个结点&#xff0c;如果将这个点删除后&a…

PostgreSQL入门到实战-第十四弹

PostgreSQL入门到实战 PostgreSQL数据过滤(七)官网地址PostgreSQL概述PostgreSQL中BETWEEN 命令理论PostgreSQL中BETWEEN 命令实战更新计划 PostgreSQL数据过滤(七) BETWEEN运算符允许您检查值是否在值的范围内。 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容…

嵌入式网线连接——笔记本电脑设置

一、需求 我们调试很多设备经常需要用到网线去调试&#xff0c;当然主流是USB&#xff0c;和网线。 二、笔记本电脑端设备 有网口的&#xff0c;非常方便&#xff0c;如果没有网口&#xff0c;则需要用到USB转网口 连接指示灯&#xff1a; 绿色&#xff1a;灯亮表示连接正常…

风险评估在应对网络安全威胁中扮演着重要的角色

如今&#xff0c;IT 安全专家面临各种重大威胁&#xff0c;从勒索软件、网络钓鱼&#xff0c;到对基础设施的攻击&#xff0c;再到对知识产权、客户数据的窃取&#xff1b;从不安全的供应链合作伙伴&#xff0c;再到组织内部人员的恶意行为。同时&#xff0c;随着云计算、远程工…

Springboot上传集合,集合超过256直接下标越界

Springboot上传集合&#xff0c;集合超过256直接下标越界 解决方法一 单个controller生效解决方法二 全局controller生效 org.springframework.beans.InvalidPropertyException: Invalid property files[256] of bean class [analysis.vo.wcase.InsertCase]: Invalid list inde…

顺序存储结构的读取、插入与删除

顺序线性表--L已经存在&#xff0c;且1 < index < ListLength(L) 一、获得元素操作--GetElem 含义&#xff1a;将线性表L中的第 index 个位置元素值返回 思路&#xff1a;只要 index 的数值在数组下标值范围内&#xff0c;把数组第 index-1下标的值返回即可 二、插入…

最近公共祖先(LCA)

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。 输入格式 第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。 接下来 N−1 行每行包含两个正整数x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以…