当前位置: 首页 > news >正文

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 运行时提供的函数,用于实现条件变量的等待和通知功能。

内部实现细节

  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 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 结构体中,该结构体包含以下字段:

  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 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结束")
}

实现原理

  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 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 不需要显式的加锁来保证线程安全性,这使得它非常适合用于高并发环境下的键值对存储。

使用场景

  1. 并发读写:

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

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

    • 在多线程环境中,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语言中用于传递取消信号、截止时间、超时时间以及请求范围内的值的重要工具。

使用场景

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

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

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

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

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

使用示例

取消长时间运行的任务
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…...

计算机学生高效记录并整理编程学习笔记的方法

哪些知识点需要做笔记&#xff1f; 以下是我认为计算机学生大学四年可以积累的笔记。 ① 编程语言类&#xff08;C语言CJava&#xff09;&#xff1a;保留课堂笔记中可运行的代码部分&#xff0c;课后debug跑一跑。学习语言初期应该多写代码&#xff08;从仿写到自己写&#…...

【书生大模型实战】L2-LMDeploy 量化部署实践闯关任务

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

《编程学习笔记之道:构建知识宝库的秘诀》

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

DETR论文,基于transformer的目标检测网络 DETR:End-to-End Object Detection with Transformers

transformer的基本结构: encoder-decoder的基本流程为&#xff1a; 1&#xff09;对于输入&#xff0c;首先进行embedding操作&#xff0c;即将输入映射为向量的形式&#xff0c;包含两部分操作&#xff0c;第一部分是input embedding&#xff1a;例如&#xff0c;在NLP领域&…...

untiy有渲染线程和逻辑线程嘛

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

什么是数据仓库ODS层?为什么需要ODS层?

在大数据时代&#xff0c;数据仓库的重要性不言而喻。它不仅是企业数据存储与管理的核心&#xff0c;更是数据分析与决策支持的重要基础。而在数据仓库的各个层次中&#xff0c;ODS层&#xff08;Operational Data Store&#xff0c;操作型数据存储&#xff09;作为关键一环&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# 保留第一个位置&#xff0c;剩下数字的组合leftCom math.factorial(n - 1) #用于计算 (n-1) 的阶乘值ele k // leftCommod k % leftCo…...

PCL 三线性插值

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

JVM虚拟机(一)介绍、JVM内存模型、JAVA内存模型,堆区、虚拟机栈、本地方法栈、方法区、常量池

目录 学习JVM有什么用、为什么要学JVM&#xff1f; JVM是什么呢&#xff1f; 优点一&#xff1a;一次编写&#xff0c;到处运行。&#xff08;Write Once, Run Anywhere&#xff0c;WORA&#xff09; 优点二&#xff1a;自动内存管理&#xff0c;垃圾回收机制。 优点三&am…...

Python利用xlrd复制一个Excel中的sheet保留原格式创建一个副本(注:xlrd只能读取xls)

目录 专栏导读库的介绍库的安装完整代码总结 专栏导读 &#x1f338; 欢迎来到Python办公自动化专栏—Python处理办公问题&#xff0c;解放您的双手 &#x1f3f3;️‍&#x1f308; 博客主页&#xff1a;请点击——> 一晌小贪欢的博客主页求关注 &#x1f44d; 该系列文…...

40、Python之面向对象:扩展的对象属性解析顺序(描述符 + MRO)

引言 在上一篇文章中&#xff0c;我们简单回顾了Python中在继承语境下的属性解析顺序&#xff0c;同时补充了能够控制、影响属性解析的3个函数/方法&#xff08;2个魔术方法 1个内置函数&#xff09;&#xff0c;相信对Python中属性的解析&#xff0c;相较于MRO&#xff0c;有…...

stm32—时钟、定时器和看门狗

1. 时钟 什么是时钟呢&#xff1f; 一个可以产生周期性信号的设备 什么是周期性信号&#xff1f; 1 ----- ----- ----- 0 ----- ----- ----- 所以时钟信号就是周期性变化的信号 关于时钟我们有两个比较重要…...

Windows平台RTSP|RTMP播放器如何实时调节音量

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

Leetcode JAVA刷刷站(10)正则表达式匹配

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

合并图片为pdf

1.先使用IDM在网页下载&#xff1a; 2.按文件类型分组&#xff0c;在按名称大小排序&#xff0c;之后使用Acrobat合并文件成一个pdf即可...

【Linux Install】Ubuntu20, Windows10 双系统安装

1. 制作启动盘 1.1 下载 Ubuntu 系统镜像 ISO 文件 从 Ubuntu 官网下载 (https://cn.ubuntu.com/download/desktop)。官网访问慢的&#xff0c;从国内镜像点下。 1.2 烧录 Ubuntu ISO 镜像 下载 Rufus&#xff1a;从Rufus官网下载 Rufus 工具。 插入U 盘&#xff1a;将U盘插…...

Keepalived + LVS实现高可用

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

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…...

上海凯泉泵业入职测评北森题库题型分析、备考题库、高分攻略

上海凯泉泵业&#xff08;集团&#xff09;有限公司是一家大型综合性泵业公司&#xff0c;专注于设计、生产、销售泵、给水设备及其控制设备。作为中国泵行业的领军企业&#xff0c;凯泉集团拥有7家企业和5个工业园区&#xff0c;总资产达到25亿元&#xff0c;生产性建筑面积35…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数&#xff0c;对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...