Golang面试题四(并发编程)
目录
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 mainimport ("fmt""sync""time"
)// 使用场景:
// 当多个 goroutines 需要访问和修改相同的变量或数据结构时,Mutex 可以用来确保每次只有一个 goroutine 在执行修改操作。
func main() {var mu sync.Mutexcount := 0var wg sync.WaitGroupwg.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 mainimport ("fmt""sync"
)// 使用场景:
// 当多个 goroutines 需要频繁读取共享数据,而写入操作较少时,RWMutex 可以提高并发性能。
func main() {var mu sync.RWMutexcount := 0var wg sync.WaitGroupwg.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 mainimport ("fmt""sync""sync/atomic""time"
)// 原子更新整型变量
func main() {count := int64(0)var wg sync.WaitGroupwg.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 mainimport ("fmt""sync""sync/atomic""time""unsafe"
)type MyStruct struct {Name stringAge int
}func main() {count := int64(0)var wg sync.WaitGroupwg.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.Pointeratomic.StorePointer(&ptr, unsafe.Pointer(new(MyStruct)))var wgPtr sync.WaitGroupwgPtr.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 mainimport ("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 mainimport ("fmt""sync""time"
)// 使用场景:
// 当你需要确保所有并发运行的 goroutines 都完成任务后再继续执行主 goroutine 时。
func main() {var wg sync.WaitGroupwg.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 运行时提供的函数,用于实现条件变量的等待和通知功能。
内部实现细节
-
状态检查:
- 在
Add
方法中,通过原子操作检查当前任务数量是否为零。如果是零,则不需要做任何事情,直接返回。 - 如果不是零,则更新任务数量,并检查更新后的任务数量是否为零。如果是零,则唤醒所有等待者。
- 在
-
等待者处理:
- 在
Wait
方法中,当前 goroutine 成为等待者,并增加等待者数量。 - 如果此时任务数量为零,则立即返回。
- 如果任务数量不为零,则当前 goroutine 将进入阻塞状态,直到所有任务完成。
- 当任务完成时,等待者会被唤醒,并减少等待者数量。
- 在
-
原子操作:
- 使用
sync/atomic
包中的原子操作来更新状态,确保线程安全性。 - 通过
atomic.AddInt64
更新状态,通过atomic.LoadInt64
获取状态。
- 使用
-
条件变量:
- 使用
sync.runtime_notify
和sync.runtime_wait
来实现条件变量的功能,以等待或通知等待者。
- 使用
6. sync.Once
使用场景
Once 保证某个函数只被调用一次,即使有多个 goroutines 同时尝试调用该函数。
使用示例
package mainimport ("fmt""sync""time"
)// 使用场景:
// 当你想要确保某个初始化操作只执行一次时。
func main() {var once sync.Oncefor 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
结构体中,该结构体包含以下字段:
- done - 一个
uint32
类型的原子变量,用来表示是否已经执行过操作。 - m - 一个互斥锁(Mutex),用于保护内部状态不被并发修改。
sync.Once
的主要方法有两个:Do
和 Done
。
- Do 方法接收一个函数作为参数,并保证这个函数仅被执行一次。
- 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()}
}
详细解释
- 原子读取: 使用 atomic.LoadUint32(&o.done) 快速检查 done 是否为 1。如果为 1,则说明已经执行过操作了,直接返回。
- 锁定: 如果 done 不为 1,则需要获取互斥锁来确保不会同时有多个 goroutine 执行相同的操作。
- 双重检查: 在获得锁之后再次检查 done,因为可能在等待锁的过程中另一个 goroutine 已经完成了操作。
- 执行函数: 如果 done 仍然为 0,则执行函数 f 并设置 done 为 1。
- 解锁: 完成操作后释放锁。
- 通过这种方式,sync.Once 能够确保函数 f 只会被执行一次,即使在高并发环境下也能保持这种行为不变。
7. sync.Cond
sync.Cond可以让一组的Coroutine都在满足特定条件时被唤醒
使用场景
利用等待/通知机制实现阻塞或者唤醒
使用示例
package mainimport ("fmt""sync""time"
)func main() {mu := &sync.Mutex{}dataReady := falsedata := "Hello, World!"// 创建条件变量,传入互斥锁 mucond := sync.NewCond(mu)// 生产者 goroutinego func() {time.Sleep(1 * time.Second)mu.Lock()fmt.Println("生产者:数据已准备好")dataReady = true//cond.Signal()cond.Broadcast() // 数据准备好了,唤醒所有等待的消费者mu.Unlock()}()// 消费者 goroutinesconsumerCount := 3for 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结束")
}
实现原理
-
互斥锁 (
Mutex
或RWMutex
):sync.Cond
依赖于一个互斥锁(通常是一个Mutex
或RWMutex
),以确保在等待条件变量时,只有持有锁的 goroutine 才能调用Wait()
方法。 -
等待队列 (
waiterList
): 当一个 goroutine 调用Wait()
方法时,它会释放锁并被添加到等待队列中。当条件变量被Broadcast()
或Signal()
时,等待队列中的 goroutines 会被唤醒。 -
唤醒机制 (
Broadcast
和Signal
):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
使用场景
对象池化,TCP连接池、数据库连接池、Worker Pool
使用场景
package mainimport ("fmt""sync"
)// 定义一个函数来演示使用 sync.Pool
func usePool() {// 创建一个 sync.Poolvar pool sync.Poolpool.New = func() interface{} {return make([]int, 0, 100) // 初始容量为 100}// 从池中获取一个对象slice := pool.Get().([]int)// 使用 slicefor i := 0; i < 100; i++ {slice = append(slice, i)}fmt.Println("Slice contents:", slice)// 使用完毕后,将 slice 放回池中pool.Put(slice)
}func main() {// 调用 usePool 函数usePool()// 再次使用相同的 poolusePool()
}
9. sync.Map
是 Go 语言标准库中的一个线程安全的哈希表,它提供了并发安全的键值对存储功能。与传统的 map
不同,sync.Map
不需要显式的加锁来保证线程安全性,这使得它非常适合用于高并发环境下的键值对存储。
使用场景
-
并发读写:
- 当你需要一个可以被多个 goroutines 并发读写的键值对集合时,可以使用
sync.Map
。它可以在不需要手动加锁的情况下安全地读写数据。
- 当你需要一个可以被多个 goroutines 并发读写的键值对集合时,可以使用
-
缓存:
sync.Map
可以用来实现简单的缓存逻辑,特别是当缓存项的生命周期较短时。
-
配置管理:
- 在多线程环境中,
sync.Map
可以用来存储和更新配置信息
- 在多线程环境中,
使用示例
package mainimport ("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.MapsyncMap.Range(func(key, value interface{}) bool {fmt.Printf("Key: %v, Value: %v\n", key, value)return true // 继续遍历})// 更新值syncMap.Store("key1", "updated_value")// 再次遍历 sync.MapsyncMap.Range(func(key, value interface{}) bool {fmt.Printf("Key: %v, Value: %v\n", key, value)return true // 继续遍历})// 使用 LoadOrStorevalue, loaded := syncMap.LoadOrStore("key3", "default_value")if loaded {fmt.Println("Value already present:", value)} else {fmt.Println("Value added:", value)}// 使用 CompareAndSwapoldValue := "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 // 其底层依然是个最简单的mapamended bool // 标志位,标识m.dirty中存储的数据是否和m.read中的不一样,flase 相同,true不相同
}
10. context.Context
Go语言中用于传递取消信号、截止时间、超时时间以及请求范围内的值的重要工具。
使用场景
-
取消长时间运行的任务:
- 当客户端或服务器想要取消一个长时间运行的任务时,可以发送一个取消信号到
context
中,从而让任务知道应该尽早停止。
- 当客户端或服务器想要取消一个长时间运行的任务时,可以发送一个取消信号到
-
设置超时时间:
- 可以通过
context
设置请求的最大持续时间,防止请求无限期地等待。
- 可以通过
-
传递请求范围的值:
- 可以在
context
中携带与请求相关的数据,例如认证信息、跟踪ID等。
- 可以在
-
资源管理:
- 在请求完成后释放资源,比如关闭数据库连接。
使用示例
取消长时间运行的任务
package mainimport ("context""fmt""time"
)// LongRunningTask 模拟一个长时间运行的任务。
func LongRunningTask(ctx context.Context) {for {select {case <-ctx.Done():fmt.Println("Task canceled.")returndefault: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 mainimport ("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)returndefault:fmt.Fprintf(w, "Working... (%d)\n", i)time.Sleep(1 * time.Second)}}
}func main() {http.HandleFunc("/", ServerFunc)log.Fatal(http.ListenAndServe(":8080", nil))
}
传递请求范围的值
package mainimport ("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的数量
相关文章:

Golang面试题四(并发编程)
目录 1.Go常见的并发模型 2.哪些方法安全读写共享变量 3.如何排查数据竞争问题 4.Go有哪些同步原语 1. Mutex (互斥锁) 2. RWMutex (读写互斥锁) 3. Atomic 3.1.使用场景 3.2.整型操作 3.3.指针操作 3.4.使用示例 4. Channel 使用场景 使用示例 5. sync.WaitGr…...

计算机学生高效记录并整理编程学习笔记的方法
哪些知识点需要做笔记? 以下是我认为计算机学生大学四年可以积累的笔记。 ① 编程语言类(C语言CJava):保留课堂笔记中可运行的代码部分,课后debug跑一跑。学习语言初期应该多写代码(从仿写到自己写&#…...

【书生大模型实战】L2-LMDeploy 量化部署实践闯关任务
一、关卡任务 基础任务(完成此任务即完成闯关) 使用结合W4A16量化与kv cache量化的internlm2_5-7b-chat模型封装本地API并与大模型进行一次对话,作业截图需包括显存占用情况与大模型回复,参考4.1 API开发(优秀学员必做)使用Func…...

《编程学习笔记之道:构建知识宝库的秘诀》
在编程的浩瀚世界里,我们如同勇敢的探险家,不断追寻着知识的宝藏。而高效的笔记记录和整理方法,就像是我们手中的指南针,指引着我们在这片知识海洋中前行,不至于迷失方向。在这篇文章中,我们将深入探讨如何…...

DETR论文,基于transformer的目标检测网络 DETR:End-to-End Object Detection with Transformers
transformer的基本结构: encoder-decoder的基本流程为: 1)对于输入,首先进行embedding操作,即将输入映射为向量的形式,包含两部分操作,第一部分是input embedding:例如,在NLP领域&…...

untiy有渲染线程和逻辑线程嘛
之前我也这么认为,其实unity引擎是单线程的,当然后续的jobs不在考虑范围内 如果你在一个awake 或者 start方法中 延时,是会卡住主线程的 比如 其实游戏引擎有一个基础简单理解,那就是不断的进行一个循环,在这个周期循…...

什么是数据仓库ODS层?为什么需要ODS层?
在大数据时代,数据仓库的重要性不言而喻。它不仅是企业数据存储与管理的核心,更是数据分析与决策支持的重要基础。而在数据仓库的各个层次中,ODS层(Operational Data Store,操作型数据存储)作为关键一环&am…...

permutation sequence(
60. Permutation Sequence class Solution:def getPermutation(self, n: int, k: int) -> str:def rec(k, l, ans, n):if(n0): return# 保留第一个位置,剩下数字的组合leftCom math.factorial(n - 1) #用于计算 (n-1) 的阶乘值ele k // leftCommod k % leftCo…...

PCL 三线性插值
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 三线性插值是一种在三维空间中使用已知数据点进行插值的方法。它是在立方体内的插值方法,通过利用立方体的八个顶点的已知值来估算立方体内任意一点的值。三线性插值扩展了一维的线性插值和二维的双线性插值。其基…...

JVM虚拟机(一)介绍、JVM内存模型、JAVA内存模型,堆区、虚拟机栈、本地方法栈、方法区、常量池
目录 学习JVM有什么用、为什么要学JVM? JVM是什么呢? 优点一:一次编写,到处运行。(Write Once, Run Anywhere,WORA) 优点二:自动内存管理,垃圾回收机制。 优点三&am…...

Python利用xlrd复制一个Excel中的sheet保留原格式创建一个副本(注:xlrd只能读取xls)
目录 专栏导读库的介绍库的安装完整代码总结 专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文…...

40、Python之面向对象:扩展的对象属性解析顺序(描述符 + MRO)
引言 在上一篇文章中,我们简单回顾了Python中在继承语境下的属性解析顺序,同时补充了能够控制、影响属性解析的3个函数/方法(2个魔术方法 1个内置函数),相信对Python中属性的解析,相较于MRO,有…...

stm32—时钟、定时器和看门狗
1. 时钟 什么是时钟呢? 一个可以产生周期性信号的设备 什么是周期性信号? 1 ----- ----- ----- 0 ----- ----- ----- 所以时钟信号就是周期性变化的信号 关于时钟我们有两个比较重要…...

Windows平台RTSP|RTMP播放器如何实时调节音量
我们在做Windows平台RTSP、RTMP播放器的时候,有这样的技术需求,特别是多路监控的时候,并不是每一路audio都需要播放出来的,所以,这时候,需要有针对音量调节的设计: /** smart_player_sdk.cs* C…...

Leetcode JAVA刷刷站(10)正则表达式匹配
一、题目概述 二、思路方向 在Java中,实现一个支持.和*的正则表达式匹配器,可以通过递归或动态规划(DP)的方法来完成。这里,我将使用动态规划的方法来解决这个问题,因为它更容易理解和实现。 动态规划的思…...

合并图片为pdf
1.先使用IDM在网页下载: 2.按文件类型分组,在按名称大小排序,之后使用Acrobat合并文件成一个pdf即可...

【Linux Install】Ubuntu20, Windows10 双系统安装
1. 制作启动盘 1.1 下载 Ubuntu 系统镜像 ISO 文件 从 Ubuntu 官网下载 (https://cn.ubuntu.com/download/desktop)。官网访问慢的,从国内镜像点下。 1.2 烧录 Ubuntu ISO 镜像 下载 Rufus:从Rufus官网下载 Rufus 工具。 插入U 盘:将U盘插…...

Keepalived + LVS实现高可用
1、简介 LVS和Keepalived是Linux操作系统下实现高可用的负载均衡解决方案的重要工具。通过协同工作,它们能够实现一种高性能、高可用的负载均衡服务,使得用户能够透明地访问到集群中的服务。同时,它们还提供了强大的监控和故障切换功能&#…...

Gin框架接入Prometheus,grafana辅助pprof检测内存泄露
prometheus与grafana的安装 grom接入Prometheus,grafana-CSDN博客 Prometheus 动态加载 我们想给Prometheus新增监听任务新增ginapp项目只需要在原来的配置文件下面新增ginapp相关metric 在docker compose文件下面新增 执行 docker-compose up -d curl -X POST http://lo…...

上海凯泉泵业入职测评北森题库题型分析、备考题库、高分攻略
上海凯泉泵业(集团)有限公司是一家大型综合性泵业公司,专注于设计、生产、销售泵、给水设备及其控制设备。作为中国泵行业的领军企业,凯泉集团拥有7家企业和5个工业园区,总资产达到25亿元,生产性建筑面积35…...

Linux:基础IO
目录 1. stdin & stdout & stderr 2. 系统文件I/O 1. 接口介绍 open write read close lseek 2. open函数返回值 3. 文件描述符fd 0 & 1 & 2 文件描述符的分配规则 重回定向 dup2 简易Shell的模拟实现 4. FILE 5. 再谈对文件的理解 1. stdin …...

奥运奖牌窥视
1 前言 2024巴黎奥运会已经闭幕了,中国队创纪录地获得了海外举办的奥运会的最佳成绩,我们来个管中窥豹,看看中国队从哪些项目中取得了奖牌。 2 奖牌组成 游泳真是大项,小项数量众多,比如个人自由泳就有100m、200m、4…...

RUST实现远程操作电脑手机
简介: Rust Desk 是一个开源的远程桌面软件,能够完全替代向日葵和ToDesk的功能,包括电脑控制电脑、电脑控制手机、手机控制电脑等。它是完全免费的。 下载: 需要下载 Rust Desk 的服务端和客户端安装包。 安装: 服务…...

spring01-spring容器启动过程分析
【README】 本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐; spring容器根据配置元素组装可用系统分2个阶段,包括spring容器启动, springbean实例化阶段; 本文详细分析spring容…...

RAG与LLM原理及实践(12)--- Milvus RRFRanker的使用场景及源码分析
目录 背景 rrfRanker 简介与实例 核心逻辑 实例 蕴含思想 rrfRanker VS weightedRanker rrfRanker weightedRanker 场景使用区别 RRFRanker 使用场景 weightedRanker 使用场景 代码 代码实现 运行结果 修改代码 再次运行结果 源码 源码实现 解释 Ranker 可…...

Nginx与Tomcat的区别
Nginx与Tomcat的区别 —— 经验笔记 引言 在现代Web开发中,选择合适的服务器软件对于构建高性能、可靠的应用程序至关重要。Nginx 和 Tomcat 是两种常见的服务器软件,尽管它们都可以被归类为Web服务器,但它们的设计目标和应用场景有着本质的…...

LeetCode 3151.特殊数组 I
【LetMeFly】3151.特殊数组 I 力扣题目链接:https://leetcode.cn/problems/special-array-i/ 如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 特殊数组 。 Aging 有一个整数数组 nums。如果 nums 是一个 特殊数组 ÿ…...

【产品那些事】The OX Active ASPM Platform
文章目录 前言关于OX Security产品理念 流程体验Complete Visibility:将安全无缝嵌入到SDLC中PBOMOSC&R coverageContextualized Prioritization:快速解决最关键的风险Accelerated Response:简化安全流程See Beyond the Code:…...

欢迪迈手机商城设计与开发
TOC springboot137欢迪迈手机商城设计与开发 绪论** 1.1 研究背景 当前社会各行业领域竞争压力非常大,随着当前时代的信息化,科学化发展,让社会各行业领域都争相使用新的信息技术,对行业内的各种相关数据进行科学化࿰…...

Endnote与word关联 解决方案: COM加载项-----》CWYW插件安装
1、首先说一下本次情况,office的版本是2019,后安装的Endnote 9。旧版word也可按此方法尝试。 2、先找到关键的EndNote Cwyw.dll文件。应在此目录下:C:\Program Files (x86)\EndNote X7\Product-Support\CWYW。 3、如没有EndNote Cwyw.dll文…...