Golang面试题四(并发编程)

news2024/9/21 19:07:24

目录

1.Go常见的并发模型

2.哪些方法安全读写共享变量

3.如何排查数据竞争问题

​4.Go有哪些同步原语

1. Mutex (互斥锁)

2. RWMutex (读写互斥锁)

3. Atomic

3.1.使用场景

3.2.整型操作

3.3.指针操作

3.4.使用示例

4. Channel

使用场景

使用示例

5. sync.WaitGroup

使用场景

使用示例

内部结构

关键方法

源码解析

内部实现细节

6. sync.Once

使用场景

使用示例

实现原理

源码解析

详细解释

 7. sync.Cond

使用场景

使用示例

实现原理

源码解析

Cond 结构体定义

Locker 接口

NewCond 函数

Wait 方法

Signal 方法

Broadcast 方法

8. sync.Pool

使用场景

使用场景

9. sync.Map

使用场景

使用示例

源码解析

10. context.Context

使用场景

使用示例

取消长时间运行的任务

设置请求的超时时间

传递请求范围的值

5.其他并发原语


1.Go常见的并发模型

2.哪些方法安全读写共享变量

3.如何排查数据竞争问题

 4.Go有哪些同步原语

1. Mutex (互斥锁)

Mutex 是一种常用的锁机制,它可以用来保护临界区,确保同一时间只有一个 goroutine 访问共享资源。

package main

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

// 使用场景:
// 当多个 goroutines 需要访问和修改相同的变量或数据结构时,Mutex 可以用来确保每次只有一个 goroutine 在执行修改操作。
func main() {
	var mu sync.Mutex
	count := 0

	var wg sync.WaitGroup
	wg.Add(100)

	for i := 0; i < 100; i++ {
		go func() {
			defer wg.Done()
			mu.Lock()
			count++
			fmt.Printf("Count increased to: %d\n", count)
			time.Sleep(time.Millisecond * 1) // 模拟耗时操作
			mu.Unlock()
		}()
	}

	wg.Wait()
	fmt.Println("Final count:", count)
}

2. RWMutex (读写互斥锁)

RWMutex 允许多个读操作同时进行,但是一次只能有一个写操作。这可以提高程序的性能,特别是当读操作远远多于写操作时。

package main

import (
	"fmt"
	"sync"
)

// 使用场景:
// 当多个 goroutines 需要频繁读取共享数据,而写入操作较少时,RWMutex 可以提高并发性能。
func main() {
	var mu sync.RWMutex
	count := 0

	var wg sync.WaitGroup
	wg.Add(10)

	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			if i == 5 {
				mu.Lock()
				count++
				fmt.Printf("Write operation: Count increased to: %d\n", count)
				mu.Unlock()
			} else {
				mu.RLock()
				fmt.Printf("Read operation: Current count is: %d\n", count)
				mu.RUnlock()
			}
		}()
	}

	wg.Wait()
	fmt.Println("Final count:", count)
}

3. Atomic

Atomic 提供了一组原子操作,用于在不使用锁的情况下更新某些类型的变量,这对于避免锁的竞争和提高并发性能非常有用。它是实现锁的基石。

3.1.使用场景

  • sync/atomic 包非常适合于那些需要高并发且操作简单的情况,例如计数器、标志位等。通过使用原子操作,可以显著减少锁的使用,从而提高程序的整体性能。

3.2.整型操作

对于整型变量,sync/atomic 提供了以下方法:

  • LoadInt32: 原子性地加载一个 int32 值。
  • StoreInt32: 原子性地存储一个 int32 值。
  • SwapInt32: 原子性地交换一个 int32 值并返回旧值。
  • AddInt32: 原子性地增加一个 int32 值。
  • SubInt32: 原子性地减少一个 int32 值。
  • CompareAndSwapInt32: 原子性地比较并交换一个 int32 值。

对于其他整型(int64, uint32, uint64, uintptr),也有类似的 Load, Store, Swap, Add, Sub, 和 CompareAndSwap 方法。

3.3.指针操作

对于指针,sync/atomic 提供了以下方法:

  • LoadPointer: 原子性地加载一个 unsafe.Pointer 值。
  • StorePointer: 原子性地存储一个 unsafe.Pointer 值。
  • SwapPointer: 原子性地交换一个 unsafe.Pointer 值并返回旧值。
  • CompareAndSwapPointer: 原子性地比较并交换一个 unsafe.Pointer 值。

3.4.使用示例

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

// 原子更新整型变量
func main() {
	count := int64(0)

	var wg sync.WaitGroup
	wg.Add(10)

	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			atomic.AddInt64(&count, 1)
			fmt.Printf("Count increased to: %d\n", atomic.LoadInt64(&count))
			time.Sleep(time.Millisecond * 50) // 模拟耗时操作
		}()
	}

	wg.Wait()
	fmt.Println("Final count:", atomic.LoadInt64(&count))
}
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"
)

type MyStruct struct {
	Name string
	Age  int
}

func main() {
	count := int64(0)

	var wg sync.WaitGroup
	wg.Add(10)

	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			atomic.AddInt64(&count, 1)
			fmt.Printf("Count increased to: %d\n", atomic.LoadInt64(&count))
			time.Sleep(time.Millisecond * 50) // 模拟耗时操作
		}()
	}

	wg.Wait()
	fmt.Println("Final count:", atomic.LoadInt64(&count))

	// 使用指针
	var ptr unsafe.Pointer
	atomic.StorePointer(&ptr, unsafe.Pointer(new(MyStruct)))

	var wgPtr sync.WaitGroup
	wgPtr.Add(10)

	for i := 0; i < 10; i++ {
		go func() {
			defer wgPtr.Done()
			myStruct := (*MyStruct)(atomic.LoadPointer(&ptr))
			myStruct.Age++
			fmt.Printf("Age increased to: %d\n", myStruct.Age)
			time.Sleep(time.Millisecond * 50) // 模拟耗时操作
		}()
	}

	wgPtr.Wait()
	myStruct := (*MyStruct)(atomic.LoadPointer(&ptr))
	fmt.Println("Final age:", myStruct.Age)
}

4. Channel

Channel 是 Go 中实现通信和同步的重要手段之一。它允许 goroutines 相互通信和同步。

使用场景

消息队列,数据传递,信号通知,任务编排,锁

使用示例

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)

	go func() {
		val := <-ch // 从通道接收数据
		fmt.Println("Received value:", val)
	}()

	ch <- 1 // 发送数据到通道
	time.Sleep(time.Second)
}

5. sync.WaitGroup

WaitGroup 用于等待一组 goroutines 完成它们的工作。

使用场景

当你需要确保所有并发运行的 goroutines 都完成任务后再继续执行主 goroutine 时。

使用示例

package main

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

// 使用场景:
// 当你需要确保所有并发运行的 goroutines 都完成任务后再继续执行主 goroutine 时。
func main() {
	var wg sync.WaitGroup

	wg.Add(2)
	go func() {
		defer wg.Done()
		fmt.Println("goroutine 1 done")
	}()

	go func() {
		defer wg.Done()
		time.Sleep(time.Second)
		fmt.Println("goroutine 2 done")
	}()

	wg.Wait()
	fmt.Println("All goroutines finished")
}

内部结构

sync.WaitGroup 的内部结构主要包含以下几个关键部分:

  • state0 - 一个 uint32 类型的变量,用于存储等待组的状态。这个状态包含了两个重要的信息:

    • 任务数量(即待完成的任务数量)。
    • 等待者数量(即正在等待所有任务完成的 goroutines 数量)。
  • noCopy - 一个 sync/noCopy 类型的字段,用于标记 WaitGroup 不应被复制。

关键方法

sync.WaitGroup 提供了几个关键的方法:

  • Add(delta int) - 增加或减少待完成的任务数量。如果 delta 为正,则增加;如果为负,则减少。当 delta 为负且减少了任务数量使得任务数量变为零时,会唤醒所有的等待者。

  • Done() - 减少任务数量,通常用于表示一个任务已经完成。这相当于调用 Add(-1)

  • Wait() - 阻塞当前 goroutine,直到所有任务完成。如果当前没有任务,那么 Wait() 方法会立即返回。

源码解析

// 结构体
type WaitGroup struct {
    // 一个 sync/noCopy 类型的字段,用于标记 WaitGroup 不应被复制
    noCopy noCopy

    // state0 保存两个 32 位值的组合:
    // 低 32 位保存未完成的任务数量,
    // 高 32 位保存等待者的数量。
    state0 uint32
}

// Add方法
// Add 方法负责更新任务数量,并在适当的时候唤醒等待者:
func (wg *WaitGroup) Add(delta int) {
    // 从 state0 中获取当前的任务数量和等待者数量。
    old := atomic.LoadUint32(&wg.state0)
    for {
        // 解析出任务数量。
        n := int(old)
        n += delta
        // 如果任务数量小于 0,则返回错误。
        if n < 0 {
            panic(negCount)
        }
        // 新的状态,包括更新后的任务数量和等待者数量。
        new := uint32(n) // 仅更新任务数量,等待者数量不变。
        // 使用 CAS (compare-and-swap) 更新 state0。
        if atomic.CompareAndSwapUint32(&wg.state0, old, new) {
            break
        }
        old = atomic.LoadUint32(&wg.state0) // 重试
    }

    // 如果任务数量为 0,则唤醒所有等待者。
    if n == 0 {
        notifyAll(&wg.state0)
    }
}

// Done 方法
// Done 方法实际上是对 Add(-1) 的封装:
func (wg *WaitGroup) Done() {
    wg.Add(-1)
}

// Wait 方法
// Wait 方法阻塞当前 goroutine 直到所有任务完成:
func (wg *WaitGroup) Wait() {
    // 增加等待者数量。
    old := atomic.AddUint32(&wg.state0, waiters)
    // 如果任务数量为 0,则立即返回。
    if atomic.LoadUint32(&wg.state0)&pending == 0 {
        return
    }

    // 等待直到任务完成。
    wait(&wg.state0, old)
}

// 这里的 wait 函数是内部实现,它使用条件变量来等待,具体实现如下:
// wait blocks until the state is zero.
func wait(statep *uint32, old uint32) {
    for {
        // 如果任务数量为 0,则返回。
        if atomic.LoadUint32(statep)&pending == 0 {
            return
        }

        // 进入等待状态。
        runtime_notifyWait(&statep, old)
        old = atomic.LoadUint32(statep)
    }
}

// runtime_notifyWait 和 notifyAll 是 Go 运行时提供的函数,用于实现条件变量的等待和通知功能。

内部实现细节

  1. 状态检查:

    • 在 Add 方法中,通过原子操作检查当前任务数量是否为零。如果是零,则不需要做任何事情,直接返回。
    • 如果不是零,则更新任务数量,并检查更新后的任务数量是否为零。如果是零,则唤醒所有等待者。
  2. 等待者处理:

    • 在 Wait 方法中,当前 goroutine 成为等待者,并增加等待者数量。
    • 如果此时任务数量为零,则立即返回。
    • 如果任务数量不为零,则当前 goroutine 将进入阻塞状态,直到所有任务完成。
    • 当任务完成时,等待者会被唤醒,并减少等待者数量。
  3. 原子操作:

    • 使用 sync/atomic 包中的原子操作来更新状态,确保线程安全性。
    • 通过 atomic.AddInt64 更新状态,通过 atomic.LoadInt64 获取状态。
  4. 条件变量:

    • 使用 sync.runtime_notify 和 sync.runtime_wait 来实现条件变量的功能,以等待或通知等待者。

6. sync.Once

使用场景

Once 保证某个函数只被调用一次,即使有多个 goroutines 同时尝试调用该函数。

使用示例

package main

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

// 使用场景:
// 当你想要确保某个初始化操作只执行一次时。
func main() {
	var once sync.Once

	for i := 0; i < 10; i++ {
		go func() {
			once.Do(func() {
				fmt.Println("This will be printed only once")
			})
		}()
	}

	time.Sleep(time.Second)
	fmt.Println("Done")
}

实现原理

sync.Once 类型定义在一个 once 结构体中,该结构体包含以下字段:

  1. done - 一个 uint32 类型的原子变量,用来表示是否已经执行过操作。
  2. m - 一个互斥锁(Mutex),用于保护内部状态不被并发修改。

sync.Once 的主要方法有两个:DoDone

  1. Do 方法接收一个函数作为参数,并保证这个函数仅被执行一次。
  2. Done 方法返回一个通道,当 Do 方法执行完毕后会关闭这个通道。

源码解析

type once struct {
    // done 是一个原子变量,如果操作未执行则为 0,已执行则为 1。
    done uint32

    // m 是一个互斥锁,在执行动作时持有。
    m Mutex
}

// Do 方法调用函数 f,如果这是第一次调用 Do 方法对于这个 Once 对象。
// 如果其他协程同时进入 Do,其中一个会执行 f,其他则会等待其完成。
func (o *once) Do(f func()) {
    // 如果 done 已经为 1,则直接返回,不执行任何操作。
    if atomic.LoadUint32(&o.done) == 1 {
        return
    }

    // 否则尝试获取互斥锁。
    o.m.Lock()

    // 再次检查 done 是否为 1,防止其他 goroutine 已经完成了操作。
    if atomic.LoadUint32(&o.done) != 1 {
        // 如果不是,则执行函数 f 并将 done 设置为 1。
        defer func() {
            atomic.StoreUint32(&o.done, 1)
            o.m.Unlock()
        }()
        f()
    } else {
        // 如果是,则释放锁并返回。
        o.m.Unlock()
    }
}

详细解释

  1. 原子读取: 使用 atomic.LoadUint32(&o.done) 快速检查 done 是否为 1。如果为 1,则说明已经执行过操作了,直接返回。
  2. 锁定: 如果 done 不为 1,则需要获取互斥锁来确保不会同时有多个 goroutine 执行相同的操作。
  3. 双重检查: 在获得锁之后再次检查 done,因为可能在等待锁的过程中另一个 goroutine 已经完成了操作。
  4. 执行函数: 如果 done 仍然为 0,则执行函数 f 并设置 done 为 1。
  5. 解锁: 完成操作后释放锁。
  6. 通过这种方式,sync.Once 能够确保函数 f 只会被执行一次,即使在高并发环境下也能保持这种行为不变。

 7. sync.Cond

        sync.Cond可以让一组的Coroutine都在满足特定条件时被唤醒

使用场景

        利用等待/通知机制实现阻塞或者唤醒

使用示例

package main

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

func main() {
	mu := &sync.Mutex{}
	dataReady := false
	data := "Hello, World!"

	// 创建条件变量,传入互斥锁 mu
	cond := sync.NewCond(mu)

	// 生产者 goroutine
	go func() {
		time.Sleep(1 * time.Second)
		mu.Lock()
		fmt.Println("生产者:数据已准备好")
		dataReady = true
		//cond.Signal()
		cond.Broadcast() // 数据准备好了,唤醒所有等待的消费者
		mu.Unlock()
	}()

	// 消费者 goroutines
	consumerCount := 3
	for i := 0; i < consumerCount; i++ {
		go func(id int) {
			mu.Lock()
			for !dataReady { // 如果数据没有准备好,则等待
				fmt.Printf("消费者 %d:数据未准备好,正在等待...\n", id)
				cond.Wait()
			}
			fmt.Printf("消费者 %d:数据已获取: %s\n", id, data)
			mu.Unlock()
		}(i)
	}

	time.Sleep(3 * time.Second) // 等待 goroutines 完成
	fmt.Println("主goroutine结束")
}

实现原理

  1. 互斥锁 (MutexRWMutex): sync.Cond 依赖于一个互斥锁(通常是一个 MutexRWMutex),以确保在等待条件变量时,只有持有锁的 goroutine 才能调用 Wait() 方法。

  2. 等待队列 (waiterList): 当一个 goroutine 调用 Wait() 方法时,它会释放锁并被添加到等待队列中。当条件变量被 Broadcast()Signal() 时,等待队列中的 goroutines 会被唤醒。

  3. 唤醒机制 (BroadcastSignal): Broadcast() 方法会唤醒等待队列中的所有 goroutines,而 Signal() 方法只会唤醒等待队列中的一个 goroutine。

源码解析

在标准库 sync/cond.go

Cond 结构体定义
type Cond struct {
	L Locker // 互斥锁接口
	c chan struct{} // 用于信号的通道
}
  • L 是一个 Locker 接口类型的指针,它可以是任何实现了 Lock() 和 Unlock() 方法的对象,如 Mutex 或 RWMutex
  • c 是一个无缓冲的结构体通道,用于信号的传递。
Locker 接口
type Locker interface {
    Lock()
    Unlock()
}

这是一个简单的接口,它定义了锁的基本行为。

NewCond 函数
func NewCond(c Locker) *Cond {
    return &Cond{c, make(chan struct{})}
}

New 函数接受一个 Locker 类型的参数并返回一个 Cond 实例。

Wait 方法
func (c *Cond) Wait() {
    c.L.Lock()
    c.L.Unlock()
    c.c <- struct{}{}
}

实际上,Wait 方法的实现要比上述代码复杂得多。这里简化了实现以便更容易理解。在实际的 sync/cond.go 文件中,Wait 方法会释放锁、将当前 goroutine 加入等待队列,并阻塞当前 goroutine 直到接收到信号。

Signal 方法
func (c *Cond) Signal() {
    select {
    case c.c <- struct{}{}:
    default:
    }
}

Signal 方法尝试向 c 通道发送一个信号。如果通道未满,则发送成功;否则,由于通道无缓冲,Signal 方法将立即返回。

Broadcast 方法
func (c *Cond) Broadcast() {
    for i := 0; i < len(c.c); i++ {
        select {
        case c.c <- struct{}{}:
        default:
            break
        }
    }
}

Broadcast 方法遍历 c.c 通道的长度,并尝试向通道发送信号。这会唤醒所有等待的 goroutines。

8. sync.Pool

可以将暂时将不用的对象缓存起来,待下次需要的时候直接使用,不用再次经过内存分配,复用对象 的内存,减轻GC 的压力,提升系统的性能(频繁地分配、回收内存会给 GC带来一定的负担)

使用场景

对象池化,TCP连接池、数据库连接池、Worker Pool

使用场景

package main

import (
	"fmt"
	"sync"
)

// 定义一个函数来演示使用 sync.Pool
func usePool() {
	// 创建一个 sync.Pool
	var pool sync.Pool
	pool.New = func() interface{} {
		return make([]int, 0, 100) // 初始容量为 100
	}

	// 从池中获取一个对象
	slice := pool.Get().([]int)

	// 使用 slice
	for i := 0; i < 100; i++ {
		slice = append(slice, i)
	}
	fmt.Println("Slice contents:", slice)

	// 使用完毕后,将 slice 放回池中
	pool.Put(slice)
}

func main() {
	// 调用 usePool 函数
	usePool()

	// 再次使用相同的 pool
	usePool()
}

9. sync.Map

是 Go 语言标准库中的一个线程安全的哈希表,它提供了并发安全的键值对存储功能。与传统的 map 不同,sync.Map 不需要显式的加锁来保证线程安全性,这使得它非常适合用于高并发环境下的键值对存储。

使用场景

  1. 并发读写:

    • 当你需要一个可以被多个 goroutines 并发读写的键值对集合时,可以使用 sync.Map。它可以在不需要手动加锁的情况下安全地读写数据。
  2. 缓存:

    • sync.Map 可以用来实现简单的缓存逻辑,特别是当缓存项的生命周期较短时。
  3. 配置管理:

    • 在多线程环境中,sync.Map 可以用来存储和更新配置信息

使用示例

package main

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

func main() {
	// 创建一个 sync.Map 实例
	syncMap := sync.Map{}

	// 添加键值对
	syncMap.Store("key1", "value1")
	syncMap.Store("key2", "value2")

	// 读取值
	if value, ok := syncMap.Load("key1"); ok {
		fmt.Println("Value of key1:", value)
	} else {
		fmt.Println("Key1 not found")
	}

	// 删除键值对
	syncMap.Delete("key2")

	// 遍历 sync.Map
	syncMap.Range(func(key, value interface{}) bool {
		fmt.Printf("Key: %v, Value: %v\n", key, value)
		return true // 继续遍历
	})

	// 更新值
	syncMap.Store("key1", "updated_value")

	// 再次遍历 sync.Map
	syncMap.Range(func(key, value interface{}) bool {
		fmt.Printf("Key: %v, Value: %v\n", key, value)
		return true // 继续遍历
	})

	// 使用 LoadOrStore
	value, loaded := syncMap.LoadOrStore("key3", "default_value")
	if loaded {
		fmt.Println("Value already present:", value)
	} else {
		fmt.Println("Value added:", value)
	}

	// 使用 CompareAndSwap
	oldValue := "updated_value"
	newValue := "new_updated_value"
	if swapped := syncMap.CompareAndSwap("key1", oldValue, newValue); swapped {
		fmt.Println("Value updated:", newValue)
	} else {
		fmt.Println("Value not updated")
	}

	// 等待一段时间,让其他 goroutines 完成
	time.Sleep(1 * time.Second)
}

源码解析

// entry 键值对中的值结构体
type entry struct {
	p unsafe.Pointer // 指针,指向实际存储value值的地方
}
// Map 并发安全的map结构体
type Map struct {
	mu sync.Mutex // 锁,保护read和dirty字段

	read atomic.Value // 存仅读数据,原子操作,并发读安全,实际存储readOnly类型的数据

	dirty map[interface{}]*entry // 存最新写入的数据

	misses int // 计数器,每次在read字段中没找所需数据时,+1
	// 当此值到达一定阈值时,将dirty字段赋值给read
}

// readOnly 存储map中仅读数据的结构体
type readOnly struct {
	m       map[interface{}]*entry // 其底层依然是个最简单的map
	amended bool                   // 标志位,标识m.dirty中存储的数据是否和m.read中的不一样,flase 相同,true不相同
}

10. context.Context

Go语言中用于传递取消信号、截止时间、超时时间以及请求范围内的值的重要工具。

使用场景

  1. 取消长时间运行的任务

    • 当客户端或服务器想要取消一个长时间运行的任务时,可以发送一个取消信号到context中,从而让任务知道应该尽早停止。
  2. 设置超时时间

    • 可以通过context设置请求的最大持续时间,防止请求无限期地等待。
  3. 传递请求范围的值

    • 可以在context中携带与请求相关的数据,例如认证信息、跟踪ID等。
  4. 资源管理

    • 在请求完成后释放资源,比如关闭数据库连接。

使用示例

取消长时间运行的任务
package main

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

// LongRunningTask 模拟一个长时间运行的任务。
func LongRunningTask(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println("Task canceled.")
			return
		default:
			fmt.Println("Working...")
			time.Sleep(1 * time.Second)
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	go LongRunningTask(ctx)

	// 等待一段时间后取消任务
	time.Sleep(5 * time.Second)
	cancel()

	// 主goroutine等待一段时间以确保子goroutine有时间退出
	time.Sleep(1 * time.Second)
	fmt.Println("Main goroutine finished.")
}
设置请求的超时时间

比如http请求和数据库连接超时

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"
)

// ServerFunc 是一个简单的服务函数,它模拟一些耗时的操作。
func ServerFunc(w http.ResponseWriter, r *http.Request) {
	// 从请求中获取上下文
	ctx := r.Context()

	// 设置超时时间为5秒
	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	// 模拟一些耗时的工作
	for i := 0; ; i++ {
		select {
		case <-ctx.Done():
			http.Error(w, "Request timed out", http.StatusRequestTimeout)
			return
		default:
			fmt.Fprintf(w, "Working... (%d)\n", i)
			time.Sleep(1 * time.Second)
		}
	}
}

func main() {
	http.HandleFunc("/", ServerFunc)

	log.Fatal(http.ListenAndServe(":8080", nil))
}
传递请求范围的值
package main

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

// ProcessRequest 模拟处理一个带有请求范围值的请求。
func ProcessRequest(ctx context.Context) {
	requestID, _ := ctx.Value("request_id").(string)
	fmt.Printf("Processing request with ID: %s\n", requestID)
	time.Sleep(1 * time.Second)
	fmt.Println("Request processed.")
}

func main() {
	ctx := context.WithValue(context.Background(), "request_id", "12345")

	go ProcessRequest(ctx)

	// 主goroutine等待一段时间以确保子goroutine完成
	time.Sleep(2 * time.Second)
	fmt.Println("Main goroutine finished.")
}

5.其他并发原语

Semaphore用于控制goroutine的数量

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

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

相关文章

Java同城生鲜配送物流配送到店独立骑手端系统小程序源码

&#x1f69a;【一键解锁新鲜生活&#xff01;同城生鲜配送系统全揭秘】&#x1f966;&#x1f680; &#x1f50d; 源码揭秘&#xff1a;打造高效生鲜配送的秘密武器 &#x1f527; 想要在家就能享受超市般的生鲜盛宴吗&#xff1f;揭秘同款城生鲜配送系统的源码&#xff0c…

信号与线性系统实验一:LTI连续系统时域响应测试与分析

文章目录 一、实验目的二、实验内容与原理&#xff08;简单列了一下提纲&#xff09;第一部分&#xff1a;连续系统时域响应MATLAB仿真分析第二部分&#xff1a;连续系统时域响应Multisim电路仿真分析 三、实验器材四、实验步骤第一部分&#xff1a;连续系统时域响应MATLAB仿真…

vulnhub系列:sp eric

vulnhub系列&#xff1a;sp eric 靶机下载 一、信息收集 nmap扫描存活&#xff0c;根据mac地址寻找IP nmap 192.168.23.0/24nmap扫描端口&#xff0c;开放端口&#xff1a;22、80 nmap 192.168.23.189 -p- -A -sV -Pndirb 扫描目录&#xff0c;.git 源码&#xff0c;admin…

【python】PyQt5中单行文本输入控件QLineEdit的详细解析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

使用Python打造简易Web服务器

目录 准备工作 创建Web服务器 示例代码 运行服务器 结论 在开发过程中&#xff0c;了解Web服务器的工作原理是非常有用的。Python作为一个功能强大的编程语言&#xff0c;提供了http.server模块&#xff0c;让我们能够快速地搭建一个简易的Web服务器。本文将指导你如何使用…

【微服务】Spring Cloud Alibaba 的介绍以及和主要功能

文章目录 引言主要功能1. **服务发现与注册**2. **分布式配置管理**3. **流量管理与熔断限流**4. **消息驱动**5. **分布式事务管理**6. **远程调用&#xff08;RPC&#xff09;**7. **服务网关**8. **对象存储**9. **全链路跟踪**10. **阿里巴巴中间件支持**11. **高可用与容错…

LInux - 一文了解 ssh端口敲门knock

文章目录 基本概念工作原理实操注意事项 基本概念 SSH端口敲门技术是一种网络安全措施&#xff0c;用于防止未经授权的访问。通过端口敲门&#xff0c;可以动态地在防火墙上打开指定端口&#xff08;如SSH端口&#xff09;&#xff0c;仅允许符合特定敲门序列的用户访问。此技…

算法:魔法字典

1️⃣要求&#xff1a; 设计一个使用单词列表进行初始化的数据结构&#xff0c;单词列表中的单词 互不相同 。 如果给出一个单词&#xff0c;请判定能否只将这个单词中一个字母换成另一个字母&#xff0c;使得所形成的新单词存在于你构建的字典中。 实现 MagicDictionary 类…

Spring中WebSocket的使用

文章目录 前言什么是 WebSocketWebSocket 协议和 HTTP 协议的区别WebSocket 原理解析WebSocket 报文格式 Spring 中 WebSocket 的使用前后端发送的数据的数据类型是对象该如何做使用websocket协议如何获取到HTTP协议中的HttpSession WebSocket使用的完整代码 前言 我们在使用 …

docker基本管理和应用

一、docker是什么&#xff1a; 1.docker是什么&#xff1a;一个开源的应用容器引擎&#xff0c;基于go语言开发的&#xff0c;docker运行在linux的容器化工具&#xff0c;可以理解为轻量级的一个虚拟机。可以在任何主机上轻松创建的一个轻量级、可移植的自给自足的容器&#x…

【Tor】使用Debian系统搭建obfs4 Bridge网桥

你好 我是无聊的木子。 目录 前言 写作の原因 网桥是个啥&#xff1f; 正文 - 到底咋搭建捏 搞台机子先 比较简便の方法 - 买台云服务器 首月五折 一元试用 远程连接服务器 更加复杂の办法 - 自己拿物理机做网桥 开始搭建网桥 先安装Tor 然后配置网桥 最后组合网桥…

【阿旭机器学习实战】【38】支持向量机SVM实现手写数字识别,模型训练、评估,以及参数调优全流程

《------往期经典推荐------》 一、【100个深度学习实战项目】【链接】&#xff0c;持续更新~~ 二、机器学习实战专栏【链接】&#xff0c;已更新31期&#xff0c;欢迎关注&#xff0c;持续更新中~~ 三、深度学习【Pytorch】专栏【链接】 四、【Stable Diffusion绘画系列】专…

React之简易笔记本

此文使用React实现简易笔记本&#xff0c;包括环境配置&#xff0c;前台界面和后台应用等内容。其中后台应用主要功能是数据库操作&#xff0c;前台应用的主要功能是显示&#xff0c;增加&#xff0c;删除&#xff0c;更新数据 &#xff0c;效果如下所示&#xff1a; 一、数据…

Android Framework之Pkms详解

PKMS是Android系统中负责安装包管理的服务&#xff0c;它的主要职责如下&#xff1a; 管理系统安装的所有应用程序&#xff0c;包括升级、安装、卸载 根据Intent匹配相应的Activity、Service、Provider和BroadcastReceiver等&#xff0c;并提供相关信息 解析应用权限&#xff…

深入探讨进程间通信的重要性:理解不同的通信机制(下)

前言 在上一篇文章中&#xff0c;我们探讨了进程间通信的三种常见机制&#xff1a;管道、消息队列和共享内存。我们了解到&#xff0c;这些机制各有其特点和适用场景&#xff0c;可以根据实际需求选择合适的机制进行进程间通信。然而&#xff0c;进程间通信并不仅限于这三种方…

Zookeeper学习、Tomcat

怎样使用Zookeeper实现服务发现&#xff1f; 典型回答 服务发现是ZK的重要用途之一&#xff0c;当我们想要基于zk实现服务发现时&#xff0c;一般可以参考以下步骤&#xff1a;1. 向Zookeeper注册服务 服务提供者需要在Zookeeper上创建一个临时节点来注册自己的服务。节点的名…

第五届IEEE先进电气和能源系统国际会议(AEES 2024)即将召开!

第五届先进电气和能源系统国际会议将于2024年11月29日至12月1日在中国兰州召开&#xff0c;欢迎参加&#xff01; 本届会议关注先进电气和能源系统的新理论及其应用&#xff0c;为相关领域的技术及相关研究领域的专家、学者交流最新研究成果、探讨学术发展方向提供一个广泛的交…

LVS原理——详细介绍

目录 介绍 lvs简介 LVS作用 LVS 的优势与不足 LVS概念与相关术语 LVS的3种工作模式 LVS调度算法 LVS-dr模式 LVS-tun模式 ipvsadm工具使用 实验 nat模式集群部署 实验环境 webserver1配置 webserver2配置 lvs配置 dr模式集群部署 实验环境 router 效果呈现…

漏洞复现-Cacti命令执行漏洞 (CVE-2022-46169)

1.漏洞描述 Cacti是一套基于PHP&#xff0c;MySQL&#xff0c;SNMP及RRDTool开发的网络流量监测图形分析工具&#xff0c;可为用户提供强大且可扩展的操作监控和故障管理框架。 该漏洞存在于remote_agent.php文件中&#xff0c;未经身份验证的恶意攻击者可以通过设置HTTP_变量…

Vue2计算属性与Vue3的计算属性对比

Vue2的计算属性 在Vue2文档上存在这么一个例子&#xff1a;通过计算属性来获取全名 var vm new Vue({el: #demo,data: {firstName: Foo,lastName: Bar},computed: {fullName: function () {return this.firstName this.lastName}} }) 同时&#xff0c;如果我们更改了计算…