Golang面试题三(并发编程)

目录

1.Mutex有几种状态

未锁定(Unlocked):

锁定(Locked):

饥饿(Starvation):

2.Mutex的模式

正常模式(Normal Mode)

饥饿模式(Starvation Mode)

3.Mutex 自旋锁

4.RWMutex 实现原理

1. 双重锁机制

2. 读锁(RLock)操作

3. 写锁(Lock)操作

4. 读锁(RUnlock)和写锁(Unlock)操作

5. 饥饿保护

总结

5.RWMutex 注意事项

读写分离:

避免死锁:

避免读写锁升级:

适时释放锁:

避免过度使用写锁:

监控锁竞争:

并发安全的初始化:

并发安全的结构体嵌入:

6.sync.Cond 是什么,有什么作用,使用示例

是什么

作用

示例

7.Broadcast 和 Signal 区别

8.sync.Cond 中 Wait

基本行为

9.WaitGroup 用法

基本用法

初始化

增加计数wg.Add()

启动 Goroutine,调用 Done()

等待所有 Goroutine 完成,调用 Wait() 方法阻塞

10.WaitGroup 底层原理

结构体

noCopy

state atomic.Uint64

 sema uint32

11.什么是 sync.Once

12.什么是原子操作

13.什么是CAS

14.原子操作和锁的区别

15.sync.Pool 有什么用


1.Mutex有几种状态

Go 语言中的 sync.Mutex(互斥锁)是一种用于保护共享资源免受并发访问的同步原语。Mutex 有以下几种状态:

  1. 未锁定(Unlocked)

    • 初始状态或刚刚解锁后的状态,表示没有任何 Goroutine 正在持有锁。
    • 在此状态下,多个 Goroutine 可以同时尝试获取锁,只有一个 Goroutine 能成功获得锁并进入锁定状态。
  2. 锁定(Locked)

    • 当一个 Goroutine 成功调用 Mutex.Lock() 方法后,Mutex 进入锁定状态。
    • 在此状态下,该 Goroutine 拥有对受保护资源的独占访问权,其他尝试获取锁的 Goroutine 将被阻塞,直到锁被解锁。
    • 锁定状态下的 Mutex 只能由持有锁的 Goroutine 解锁,即通过调用 Mutex.Unlock() 方法。
  3. 饥饿(Starvation)

    • 在某些情况下,如果有多个 Goroutine 长时间争抢同一把锁,且每次都是同一个 Goroutine 获得锁,可能导致其他 Goroutine 长时间等待而得不到锁,这种情况被称为“锁饥饿”。
    • Go 语言的 sync.Mutex 实现包含了一种饥饿模式(starvation mode),当检测到可能存在锁饥饿现象时,会切换到公平调度模式,优先让等待时间最长的 Goroutine 获取锁,以缓解饥饿问题。
    • 饥饿模式是 Mutex 内部的一种特殊状态,对应用程序而言是透明的,但会影响锁的调度策略。

需要注意的是,上述状态是逻辑上的描述,实际的 sync.Mutex 结构体内部并不直接存储这些状态标识,而是通过特定的标志位和算法来实现这些状态的管理。例如,通过一个整型变量(通常称为 state 或类似名称)的低位来表示锁定状态,高位可能用于存储饥饿状态和其他内部信息。

总结来说,Go 语言 sync.Mutex 主要有三种状态:未锁定、锁定和饥饿。这些状态反映了互斥锁在不同时间段对共享资源的控制情况以及对并发 Goroutine 访问的管理状态。通过正确使用 Lock() 和 Unlock() 方法,可以确保在多线程环境下对共享资源的访问是有序且互斥的,避免数据竞争和同步问题。当遇到锁饥饿问题时,Go 语言的 Mutex 实现会自动切换到公平调度模式,尽可能减少长时间等待的 Goroutine 的等待时间。

2.Mutex的模式

Go 语言中的 sync.Mutex(互斥锁)有两种模式:

  1. 正常模式(Normal Mode)

    在正常模式下,当多个 Goroutine 同时尝试获取 Mutex 时,它们会被安排在一个隐式的等待队列中。尽管 Go 语言文档和源码注释提到 Mutex 尝试遵循“先进先出”(FIFO)原则来决定哪个 Goroutine 最终获得锁,但实际上,这种“FIFO”并非严格意义上的队列顺序,而是近似的。这是因为锁的争用发生在操作系统层面,实际的调度可能受到操作系统的调度策略和硬件影响,新到来的 Goroutine 仍有可能“插队”先获得锁。因此,正常模式下的 Mutex 被认为是非公平锁。

    正常模式适用于大多数短时、轻量级的锁竞争场景,其设计倾向于在多数情况下提供较好的性能,允许新到来的 Goroutine 快速获得锁,从而减少整体的锁等待时间。

  2. 饥饿模式(Starvation Mode)

    当 Mutex 发现存在锁饥饿现象(即某个 Goroutine 长时间未能获得锁)时,会自动切换到饥饿模式。在饥饿模式下,Mutex 会优先让等待时间最长的 Goroutine 获得锁,以缓解饥饿问题。这种模式更接近公平锁,即等待时间最长的 Goroutine 有更高的概率先获得锁,从而减少了长时间等待的 Goroutine 被持续忽略的可能性。

    饥饿模式主要适用于存在长时、重度锁竞争,或者需要避免特定 Goroutine 长期等待锁的场景,有助于避免锁饥饿问题,保证等待时间较长的 Goroutine 能够获得锁,从而改善系统的响应时间和公平性。

    触发条件:当一个 Goroutine 在连续多次尝试获取锁的过程中始终未能成功,并且其等待时间超过一定阈值(目前为大约 1 毫秒)时,Mutex 会判断可能存在锁饥饿,进而切换到饥饿模式。

    退出条件:当一个 Goroutine 在饥饿模式下成功获取并释放锁后,Mutex 通常会回到正常模式。这是因为一旦长期等待的 Goroutine 获得了锁,锁饥饿问题被认为暂时得到了缓解。

总结来说,Go 语言 sync.Mutex 提供了正常模式和饥饿模式两种操作模式。正常模式在一般情况下提供较好的性能,但可能造成锁饥饿;而饥饿模式则在检测到锁饥饿时介入,优先让等待时间最长的 Goroutine 获得锁,以保证公平性和响应时间。这两种模式的切换由 Mutex 内部自动管理,对开发者来说是透明的。在编写并发程序时,应合理设计锁的使用,避免过度竞争和长时间等待,以充分利用 Mutex 的正常模式。当确实需要避免锁饥饿时,Go 语言的 Mutex 实现能够自动调整到饥饿模式,确保系统的稳定性和公平性。

3.Mutex 自旋锁

在 Go 语言中,sync.Mutex(互斥锁)的实现并不直接支持自旋(spin),即在无法获得锁时,goroutine 不会持续地循环检查锁状态,而是会主动进入休眠状态,等待被唤醒。这是与某些其他编程语言(如 C++、Java 等)中的自旋锁有所区别的地方。

Go 语言的 sync.Mutex 在遇到锁竞争时,goroutine 会经历以下过程:

  1. 尝试获取锁:goroutine 首先尝试通过原子操作直接获取锁。如果此时锁未被其他 goroutine 持有,goroutine 可以立即获得锁并继续执行。

  2. 阻塞等待:如果锁已被其他 goroutine 持有,goroutine 不会进行自旋,而是直接调用 Go 运行时的系统调用(如 runtime.futex 等)将自己置于休眠状态,并加入到锁的等待队列中。此时,goroutine 不再消耗 CPU 时间,等待被操作系统的调度器唤醒。

  3. 被唤醒并重新尝试获取锁:当持有锁的 goroutine 释放锁时,会唤醒等待队列中的一个或多个 goroutine。被唤醒的 goroutine 重新尝试获取锁,如果此时锁仍未被其他 goroutine 持有,goroutine 就能成功获得锁并继续执行。

综上所述,Go 语言标准库中的 sync.Mutex 不允许自旋。goroutine 在无法立即获得锁时,会立即进入休眠状态,而不是持续循环检查锁状态。这样做可以避免在锁竞争激烈时,大量 goroutine 无谓地消耗 CPU 资源,有助于提升系统的整体效率和资源利用率。

如果你需要实现类似自旋锁的功能,可以使用 Go 语言标准库中的 sync/atomic 包提供的原子操作,或者使用第三方库提供的自旋锁实现。但请注意,自旋锁在某些特定场景(如持有锁的代码执行时间非常短,且预期锁竞争不严重)下可以提高效率,但滥用自旋锁可能导致 CPU 使用率过高、能源浪费以及系统响应性下降等问题,应谨慎使用。在大多数情况下,Go 语言推荐使用其内置的同步原语(如 sync.Mutexsync.RWMutexsync.WaitGroup 等)来管理并发。

package mainimport ("runtime""sync/atomic""time"
)type SpinLock uint32// Lock 尝试获取锁,如果锁已经被持有,则会自旋等待直到锁释放
func (sl *SpinLock) Lock() {for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {runtime.Gosched() // 不要占满整个CPU,让出时间片}
}// Unlock 释放锁
func (sl *SpinLock) Unlock() {atomic.StoreUint32((*uint32)(sl), 0)
}// NewSpinLock 创建一个自旋锁
func NewSpinLock() *SpinLock {return new(SpinLock)
}func main() {lock := NewSpinLock()lock.Lock()// 临界区time.Sleep(1 * time.Second) // 模拟临界区操作lock.Unlock()
}

4.RWMutex 实现原理

sync.RWMutex(读写互斥锁)是 Go 语言标准库提供的一个并发控制工具,它允许多个读取者同时访问受保护的资源,但同一时刻只允许一个写入者访问。RWMutex 的实现原理基于以下几点:

1. 双重锁机制

RWMutex 内部包含两个锁:一个用于写操作的互斥锁(Mutex),一个用于读操作的计数器(readerCount)。写锁(Mutex)确保同一时刻只有一个写入者,而读锁(readerCount)记录当前持有读锁的 Goroutine 数量。

2. 读锁(RLock)操作

当 Goroutine 调用 RLock() 方法获取读锁时,执行以下步骤:

  • 递增读者计数:使用原子操作(如 atomic.AddInt32)递增 readerCount,表示一个新读取者加入。

  • 检查写锁状态:如果此时没有写入者持有锁(即写锁未被锁定),读取者可以直接返回,开始读取操作。

  • 等待写锁释放:如果有写入者正在持有锁,读取者会被阻塞,加入到等待队列中,直到写锁被释放。

3. 写锁(Lock)操作

当 Goroutine 调用 Lock() 方法获取写锁时,执行以下步骤:

  • 获取写锁:尝试锁定写锁(Mutex),如果此时没有其他写入者或读取者持有锁,写入者可以直接获得锁,开始写入操作。

  • 等待读锁释放:如果有读取者正在持有锁,写入者会被阻塞,等待所有读取者释放其读锁。当最后一个读取者释放读锁时,readerCount 会降为 0,写入者可以继续执行。

  • 阻塞其他读写者:一旦写入者获得锁,所有后续的读取者和写入者都会被阻塞,直到写入者完成写入操作并释放锁。

4. 读锁(RUnlock)和写锁(Unlock)操作

  • 释放读锁:调用 RUnlock() 方法时,使用原子操作递减 readerCount,表示一个读取者完成读取并释放锁。

  • 释放写锁:调用 Unlock() 方法时,直接释放写锁(Mutex),同时唤醒等待队列中的下一个读取者或写入者。

5. 饥饿保护

如同 sync.MutexRWMutex 实现也包含了饥饿模式的逻辑。当检测到存在长时间等待的写入者时,会切换到饥饿模式,优先让等待时间最长的写入者获得锁,防止读取者过度霸占锁而导致写入者饥饿。

总结

sync.RWMutex 的实现原理主要基于双重锁机制,通过一个互斥锁(Mutex)控制写操作的独占性,一个计数器(readerCount)管理读取者的并发访问。读取者可以共享读锁,写入者则独占写锁,同时写入者会阻塞所有读取者。读写锁的获取和释放操作均通过原子操作保证线程安全,同时包含饥饿保护机制,确保在高并发场景下既能高效利用资源,又能避免特定 Goroutine 长时间等待锁。

5.RWMutex 注意事项

使用 Go 语言中的 sync.RWMutex(读写互斥锁)时,应注意以下事项以确保正确、高效地使用读写锁,并避免潜在的并发问题:

  1. 读写分离

    • 读操作:多个 Goroutine 可以同时进行读操作。当一个 Goroutine 持有读锁时,其他 Goroutine 仍可以获取读锁进行读取。读锁不会阻塞其他读锁的获取。
    • 写操作:任何时候只能有一个 Goroutine 持有写锁。当一个 Goroutine 持有写锁时,其他 Goroutine(包括读操作)都无法获取任何锁,直到写锁被释放。
  2. 避免死锁

    • 避免循环依赖:确保 Goroutines 在获取锁时不会形成循环等待。例如,一个 Goroutine 持有读锁时不应尝试获取写锁,否则可能造成死锁。
    • 合理顺序:如果多个锁需要按特定顺序获取,应遵守该顺序。例如,先获取写锁再获取读锁,或者先获取读锁再获取写锁,但不应在持有写锁时尝试获取读锁,反之亦然。
  3. 避免读写锁升级

    • 避免升级读锁为写锁sync.RWMutex 不支持锁升级,即一个 Goroutine 在持有读锁时不能直接升级为写锁。如果需要进行写操作,应先释放读锁,然后再获取写锁。
  4. 适时释放锁

    • 在完成读写操作后立即释放锁:确保在读取或修改共享数据后及时调用 RUnlock() 或 Unlock() 方法释放锁,避免长时间持有锁导致其他 Goroutines 阻塞。
    • 在 defer 语句中释放锁:为了确保锁总能被正确释放,推荐在获取锁的同一作用域内使用 defer 语句安排锁的释放。
  5. 避免过度使用写锁

    • 合理评估操作性质:如果一个操作只是读取数据,不需要修改,应使用读锁而非写锁。过度使用写锁会阻塞其他读操作,降低系统的并发性能。
    • 批量操作:如果一系列操作可以视为一个原子的读或写操作,应尽量一次性获取相应类型的锁,避免在操作过程中反复切换锁类型。
  6. 监控锁竞争

    • 检测锁等待时间:通过性能分析工具(如 pprof)监控锁的等待时间,如果发现等待时间过长,可能存在锁竞争问题,需要优化锁的使用或数据结构设计。
  7. 并发安全的初始化

    • 在使用前初始化:确保 sync.RWMutex 变量在任何 Goroutine 访问之前已经被正确初始化。未初始化的锁可能导致数据竞争和程序崩溃。
  8. 并发安全的结构体嵌入

    • 避免嵌入锁的结构体被复制:如果一个结构体中嵌入了 sync.RWMutex,并且该结构体被复制(如通过赋值或作为函数参数传递),复制后的结构体会带有独立的锁,可能导致数据不一致。应确保通过指针传递和操作包含锁的结构体。

总结来说,使用 sync.RWMutex 时应明确区分读写操作,避免死锁和锁升级,及时释放锁,合理使用读锁以提高并发性,并密切关注锁的竞争情况以进行性能调优。同时,要注意锁的初始化、结构体嵌入的并发安全问题。遵循这些注意事项,可以确保在多 Goroutine 环境下正确、高效地使用读写锁来保护共享资源。

6.sync.Cond 是什么,有什么作用,使用示例

是什么

sync.Cond 是 Go 语言中用于实现基于条件的同步的一种工具,它与互斥锁配合使用,使得 Goroutines 能够在满足特定条件时进行协作。通过 Wait()Signal() 和 Broadcast() 方法,Goroutines 可以在条件不满足时挂起等待,条件满足时被唤醒继续执行,有效地避免了忙等待,提高了并发环境下的资源利用率和程序性能。在实际编程中,条件变量常用于解决生产者-消费者、任务队列、事件通知等场景的同步问题。

作用

sync.Cond 的主要作用如下:

  1. 条件等待:当某个共享状态的条件不满足时,Goroutine 可以调用 Cond.Wait() 方法挂起自身,进入等待状态,直到其他 Goroutine 修改了共享状态,使得条件变为满足。

  2. 条件通知:当某个 Goroutine 修改了共享状态,使得某个等待条件变为满足时,可以调用 Cond.Signal() 或 Cond.Broadcast() 方法,通知一个或所有等待该条件的 Goroutine,使它们从等待状态恢复并继续执行。

示例

下面是一个使用 sync.Cond 的简单示例,模拟了一个生产者-消费者模型,其中消费者等待队列非空时才消费数据:

package mainimport ("fmt""sync""time"
)var done = false// 读函数
func read(name string, c *sync.Cond) {//关联锁c.L.Lock()//先让读函数等待通知for !done {fmt.Printf("【%s】 来了,等在门外\n", name)c.Wait()}fmt.Printf("【%s】 入账 ====》\n", name)c.L.Unlock()
}// 写函数
func write(name string, c *sync.Cond) {c.L.Lock()fmt.Printf("【%s】入帐,作战计划制定中~~~~~ \n", name)time.Sleep(2 * time.Second)done = truec.L.Unlock()time.Sleep(2 * time.Second)fmt.Printf("======== 作战计划制定完毕 ========\n【%s】 大家进来开会吧! \n", name)c.Broadcast()
}func main() {cond := sync.NewCond(&sync.Mutex{})fmt.Println("========= 玄德公 升帐了 =============")for _, name := range []string{"关羽", "张飞", "赵云"} {go read(name, cond)}time.Sleep(time.Second * 1)go write("孔明", cond)time.Sleep(time.Second * 10)
}

7.Broadcast 和 Signal 区别

sync.Cond 的 Broadcast() 和 Signal() 方法的主要区别在于唤醒等待者数量的不同:

  • Broadcast() 唤醒所有等待的 Goroutines。
  • Signal() 只唤醒一个等待的 Goroutine。

选择使用哪种方法取决于具体的应用场景和同步需求。如果需要通知所有等待者或发生了全局状态变化,使用 Broadcast() 更合适;如果只需要唤醒一个等待者或每次只有一个等待者的需求得到满足,使用 Signal() 更合适。在实际编程中,应根据实际的并发逻辑和资源管理需求,合理选择使用 Broadcast() 或 Signal(),以确保正确的并发行为和高效的资源利用。

8.sync.Cond 中 Wait

sync.Cond 中的 Wait() 方法用于让当前 Goroutine 暂停执行并进入等待状态,直到收到 Signal() 或 Broadcast() 的通知。以下是关于 Wait() 方法的详细说明:

基本行为

  • 释放锁:在调用 Wait() 之前,必须已经获得了与 Cond 相关联的底层互斥锁(由创建 Cond 时传入的 Locker 提供)。调用 Wait() 时,会自动释放底层互斥锁,使得其他 Goroutine 有机会获得锁并修改共享状态。

  • 进入等待状态:释放锁后,当前 Goroutine 会进入等待状态,挂起执行,直到收到 Signal() 或 Broadcast() 的通知。在此期间,当前 Goroutine 不会消耗 CPU 资源,而是等待被其他 Goroutine 唤醒。

  • 重新获取锁:当收到 Signal() 或 Broadcast() 的通知后,当前 Goroutine 会重新尝试获取底层互斥锁。一旦获取到锁,Wait() 调用会立即返回,使得当前 Goroutine 继续执行后续逻辑。

9.WaitGroup 用法

sync.WaitGroup 用于等待一组 Goroutine 完成其任务。使用时,首先初始化 WaitGroup,在启动每个 Goroutine 前调用 Add() 增加计数,Goroutine 结束时调用 Done() 通知完成,最后在需要等待所有 Goroutine 完成的地方调用 Wait()。使用过程中应注意避免计数器溢出、重复调用 Done()、确保 Done() 被调用,以及避免在 Wait() 后面的代码中修改共享状态。遵循这些规则,可以有效地使用 sync.WaitGroup 实现 Goroutine 的同步等待。

基本用法

初始化

创建一个 sync.WaitGroup 变量。

var wg sync.WaitGroup

增加计数wg.Add()

在启动 Goroutine 之前,调用 Add() 方法增加计数,参数表示要启动的 Goroutine 数量。

wg.Add(3) // 假设要启动3个Goroutine

启动 Goroutine,调用 Done()

启动 Goroutine,执行所需任务。在 Goroutine 结束时,调用 Done() 方法通知 WaitGroup 任务已完成。

for i := 0; i < 3; i++ {go func(i int) {defer wg.Done() // 任务完成后通知WaitGroup// 执行任务代码}(i)
}

等待所有 Goroutine 完成,调用 Wait() 方法阻塞

在主线程或其他需要等待所有 Goroutine 完成的地方,调用 Wait() 方法阻塞,直到所有 Goroutine 调用 Done() 方法,计数器归零。

wg.Wait() // 等待所有Goroutine完成

后续操作Wait() 返回后,所有 Goroutine 已经完成任务,可以进行后续操作。

10.WaitGroup 底层原理

结构体

// WaitGroup 等待一组 Goroutine 完成。
// 主 Goroutine 调用 Add 方法设置要等待的 Goroutine 数量,
// 然后每个 Goroutine 运行并在完成后调用 Done 方法。
// 同时,可以使用 Wait 方法阻塞,直到所有 Goroutine 完成。
//
// WaitGroup 在第一次使用后不能被复制。
//
// 根据 Go 内存模型的术语,Done 调用“同步于”任何它解除阻塞的 Wait 调用的返回。
type WaitGroup struct {noCopy noCopystate atomic.Uint64 // 高 32 位是计数器, 低 32 位是等待者数量(后文解释)。sema  uint32
}

noCopy

首先是 noCopy,这个东西是为了告诉编译器,WaitGroup 结构体对象不可复制,即 wg2 := wg 是非法的。之所以禁止复制,是为了防止可能发生的死锁。但实际上如果我们对 WaitGroup 对象进行复制后,至少在 1.20 版本下,Go 的编译器只是发出警告,没有阻止编译过程,我们依然可以编译成功。 

state atomic.Uint64

state 是 WaitGroup 的核心,它是一个无符号的 64 位整型,并且用的是 atomic 包中的 Uint64,所以 state 本身是线程安全的。至于 atomic.Uint64 为什么能保证线程安全,因为它使用了 CompareAndSwap(CAS) 操作,而这个操作依赖于 CPU 提供的原子性指令,是 CPU 级的原子操作。

state 的高 32 位是计数器(counter),低 32 位是等待者数量(waiters)。其中计数器其实就是 Add(int) 数量的总和,譬如 Add(1) 后再 Add(2),那么这个计数器就是 1 + 2 = 3;而等待数量就是现在有多少 goroutine 在执行 Wait() 等待 WaitGroup 被释放。

 sema uint32

信号量

sync.WaitGroup 的底层原理基于计数器和等待队列。通过原子操作更新计数器,跟踪需要等待的 Goroutine 数量。Add() 方法增加或减少计数器,Done() 方法等同于减少计数器,Wait() 方法在计数器非零时让当前 Goroutine 进入等待状态,直到计数器归零时唤醒所有等待的 Goroutine。

参考:Golang WaitGroup 底层原理及源码解析_Golang_脚本之家 

11.什么是 sync.Once

12.什么是原子操作

13.什么是CAS

14.原子操作和锁的区别

15.sync.Pool 有什么用

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

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

相关文章

【机器学习300问】69、为什么深层神经网络比浅层要好用?

要回答这个问题&#xff0c;首先得知道神经网络都在计算些什么东西&#xff1f;之前我在迁移学习的文章中稍有提到&#xff0c;跳转链接在下面&#xff1a; 为什么其他任务预训练的模型参数&#xff0c;可以在我这个任务上起作用&#xff1f;http://t.csdnimg.cn/FVAV8 …

Elasticsearch数据写入、检索流程及底层原理全方位解析

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 ✍&#x1f3fb;序言✍&#x1f3fb;1️⃣✍&#x1f3fb;es的架构简介1. 分布式架构2. 索引与搜索3. 数据写入与持久化4. 缓…

C#值类型和引用类型、赋值、区别、相同点

C#值类型和引用类型 **前言&#xff1a;**在C#中变量分为以下几种类型&#xff1a;值类型&#xff08;Value Types&#xff09;,引用类型&#xff08;Reference Types&#xff09;,指针类型&#xff08;Pointer Types&#xff09;;指针类型&#xff08;变量存储另一种类型变量…

如何做信创测试

信创测试是一种系统化的方法&#xff0c;旨在评估和验证创意和创新项目的潜力和可行性。进行信创测试可以帮助企业在投入大量资源之前&#xff0c;对创意进行客观、科学的评估&#xff0c;以减少失败的风险并最大化成功的可能性。以下是一般性的信创测试步骤&#xff1a; 确定…

用 LLaMA-Factory 在魔搭微调千问

今天在魔搭上把千问调优跑通了&#xff0c;训练模型现在在 Mac 还不支持&#xff0c;需要用 N 卡才可以&#xff0c;只能弄个N 卡的机器&#xff0c;或者买个云服务器。魔搭可以用几十个小时&#xff0c;但是不太稳定&#xff0c;有的时候会自动停止。 注册账号 直接手机号注…

Docker:安装Redis,同时设置外网访问

step 1: 安装必要的一些系统工具   yum install -y yum-utils device-mapper-persistent-data lvm2 yum install -y libseccomp-devel Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo ​…

每天学习一个Linux命令之make

每天学习一个Linux命令之make 简介 在Linux系统中&#xff0c;make命令是一个非常有用的构建工具。它根据Makefile文件中指定的规则来构建项目。make命令可以极大地简化编译、链接和安装等任务。本文将详细介绍make命令的使用方法以及可用的选项。 基本用法 make命令的基本…

css的背景

一.css的背景&#xff1a; 通过css背景属性&#xff0c;可以给页面元素增加背景样式。 背景属性可以设置背景颜色&#xff0c;背景图片&#xff0c;背景平铺&#xff0c;背景图片位置&#xff0c;背景图像固定等。 3.1背景颜色 样式名称&#xff1a; background-color定义…

自然语言处理: 第二十七章LLM训练超参数

前言: LLM微调的超参大致有如下内容,在本文中&#xff0c;我们针对这些参数进行解释 training_arguments TrainingArguments(output_dir"./results",per_device_train_batch_size4,per_device_eval_batch_size4,gradient_accumulation_steps2,optim"adamw_8bi…

【无人机/平衡车/机器人】详解STM32+MPU6050姿态解算—卡尔曼滤波+四元数法+互补滤波(文末附3个算法源码)

效果: MPU6050姿态解算-卡尔曼滤波+四元数+互补滤波 目录 基础知识详解 欧拉角

对 SuperNeurons 的复现记录

文章目录 论文源码环境步骤数据集参考备忘 论文 [1801.04380] SuperNeurons: Dynamic GPU Memory Management for Training Deep Neural Networks 源码 GitHub - linnanwang/superneurons-release: this is the release repository of superneurons 环境 Ubuntu 22.04GeFo…

OpenCV基本图像处理操作(五)——图像数据操作

数据读取 cv2.IMREAD_COLOR&#xff1a;彩色图像cv2.IMREAD_GRAYSCALE&#xff1a;灰度图像 import cv2 #opencv读取的格式是BGR import matplotlib.pyplot as plt import numpy as np %matplotlib inline imgcv2.imread(cat.jpg)数据显示 #图像的显示,也可以创建多个窗口 c…

力扣练习题(2024/4/15)

1打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个房屋…

数组以及稀疏矩阵的快速转置算法详细分析

一.数组&#xff1a; 1.数组的地址计算&#xff1a; 数组的地址计算比较简单&#xff0c;读者可以自行了解&#xff0c;在这里不再赘述&#xff1b; 2.特殊矩阵的压缩存储&#xff1a; 在这里我们主要说明稀疏矩阵的主要内容&#xff1a; &#xff08;1&#xff09;稀疏矩阵…

[重学Python]Day 2 Python经典案例简单习题6个

[重学Python]Day 2 Python经典案例简单习题6个 一、寻找水仙花数二、百钱百鸡三、CRAPS赌博游戏四、生成**斐波那契数列**的前20个数五、找出10000以内的**完美数**六、输出**100以内所有的素数** 项目链接&#xff1a;https://github.com/jackfrued/Python-100-Days 一、寻找…

J垃圾回收

J垃圾回收 1 概述2 方法区的回收3 如何判断对象可以回收3.1 引用计数法3.2 可达性分析法 4 常见的引用对象4.1 软引用4.2 弱引用4.3 虚引用4.4 终结器引用 5 垃圾回收算法5.1 垃圾回收算法的历史和分类5.2 垃圾回收算法的评价标准5.3 标记清除算法5.4 复制算法5.5 标记整理算法…

关于UAC标准音频数据传输端点的最大包长问题

1. 标准音频数据传输端点 标准音频数据传输端点(Standard AS Isochronous Audio Data Endpoint Descriptor) 是在音频流接口下,用来传输音频数据的端点。需要注意描述符结构中的两个字段:wMaxPacketSize + bInterval 。 (1) wMaxPacketSize:端点在某一时刻,发送/接收的…

sky08、09笔记常用组合逻辑电路

本节的目的是为了更好的预估delay。 1.1bit全加器 module fadd_1b( a, b, cin, s, cout ); input wire a,b,cin; output wire s,cout;wire p,g; assign p a|b;//propagate carry assign g a&b;//generate carry assign s a^b^cin; assign cout (p&cin)|g; endmodu…

使用Python脚本检测服务器信息并定时发送至管理员邮箱

在日常的系统管理工作中&#xff0c;监测服务器的资源占用情况至关重要&#xff0c;我们需要及时获得通知以便采取相应措施。我新装了一台UbuntuServer服务器&#xff0c;写了一个可以定期收集服务器的CPU、内存、网络和磁盘信息&#xff0c;并通过邮件将这些信息发送给管理员的…

github上的软件许可证是什么?如何合并本地的分支德语难学还是俄语更加难学?站在一个中国人的立场上,德语难学还是俄语更加难学?俄语跟德语有什么样的显著差别?

目录 github上的软件许可证是什么&#xff1f; 如何合并本地的分支 德语难学还是俄语更加难学&#xff1f; 站在一个中国人的立场上&#xff0c;德语难学还是俄语更加难学&#xff1f; 俄语跟德语有什么样的显著差别&#xff1f; github上的软件许可证是什么&#xff1f; …