golang 锁实现原理与解析&锁机制(sync)种类与举例说明以及其使用场景

Go语言中的**sync包提供了并发编程所需的基本同步原语,例如互斥锁、条件变量、等待组等, 主要解决并发编程中goroutine之间的数据竞争和协调问题**、协调多个goroutine等常见问题。以下是核心组件详解及使用场景:

一.锁实现原理与解析

1.Mutex 底层结构

Go 复制代码
type Mutex struct {
    state int32  // 复合状态字段(包含锁状态、等待者数量等)
    sema  uint32 // 信号量,用于阻塞/唤醒协程
}

state 字段的二进制布局

  • 第0位:locked(是否已锁:0未锁,1已锁)

  • 第1位:woken(是否有协程被唤醒)

  • 第2位:starving(是否饥饿模式)

  • 剩余位:waiter(等待锁的协程数量)

2.锁的几种模式

  • 正常模式

    • 新来的协程会尝试**自旋(约4次)抢锁,**若失败则进入等待队列

    • 唤醒等待者时需与新来的协程竞争,可能导致等待者长期饥饿

  • 饥饿模式(当等待时间 > 1ms 时触发):

    • 新来的协程直接排队,不尝试抢锁

    • 锁直接交给等待队列最前端的协程

    • 当等待者执行完毕且无其他等待者时,切换回正常模式

3.核心方法解析

(1).Lock() 流程

Go 复制代码
func (m *Mutex) Lock() {
    // 快速路径:直接获取未锁定的mutex
    if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
        return
    }
    m.lockSlow() // 进入慢路径
}

lockSlow()中:

  • 若当前是正常模式且能自旋,则循环尝试CAS抢锁。

  • 自旋失败后累加等待计数,尝试设置锁状态。

  • 若等待超时,标记为饥饿模式

(2).Unlock() 流程

Go 复制代码
func (m *Mutex) Unlock() {
    // 快速解锁(无竞争时)
    new := atomic.AddInt32(&m.state, -mutexLocked)
    if new != 0 {
        m.unlockSlow(new) // 有等待者需处理
    }
}

unlockSlow()中:

  • 若存在等待者,根据模式决定唤醒策略:

    • 饥饿模式:直接移交锁给下一个等待者。

    • 正常模式:唤醒一个等待者并允许其与新来协程竞争。

4.场景分析

(1).基础用法(银行转账)

Go 复制代码
type Account struct {
    balance int
    mu      sync.Mutex
}

func (a *Account) Transfer(to *Account, amount int) {
    a.mu.Lock()
    defer a.mu.Unlock()
    to.mu.Lock()
    defer to.mu.Unlock()
    
    a.balance -= amount
    to.balance += amount
}

风险:嵌套锁可能导致死锁(如A转B同时B转A)。

改进 :使用**sync.RWMutex**或调整锁定顺序

(2).饥饿模式演

Go 复制代码
func main() {
    var mu sync.Mutex
    done := make(chan bool)

    // 长期持有锁的协程
    go func() {
        mu.Lock()
        time.Sleep(2 * time.Second) // 模拟耗时操作
        mu.Unlock()
        done <- true
    }()

    // 后续协程因饥饿进入等待队列
    go func() {
        time.Sleep(500 * time.Millisecond)
        mu.Lock() // 此处将触发饥饿模式
        mu.Unlock()
        done <- true
    }()

    <-done; <-done
}

(3).读写分离

Go 复制代码
var cache struct {
    data map[string]string
    rw   sync.RWMutex // 读多写少场景
}
func Get(key string) string {
    cache.rw.RLock()
    defer cache.rw.RUnlock()
    return cache.data[key]
}

(4).避免复制锁

Go 复制代码
type Container struct {
    mu sync.Mutex
    // ...
}
func main() {
    c := Container{}
    go func(copy Container) { // 错误!复制了锁
        copy.mu.Lock()       // 导致不可预测行为
    }(c)
}

5.底层机制关键点

  • 自旋优化:在正常模式下,新协程通过有限自旋(CPU空转)尝试避免进入内核阻塞,减少上下文切换开销

  • 信号量操作 :底层依赖runtime.semacquire()runtime.semrelease(),利用FIFO队列管理等待协程

  • 内存屏障 :通过atomic操作保证锁状态变更的可见性,符合Go内存模型要求

sync.Mutex通过复合状态位+信号量+模式切换实现了高效公平的锁机制:

  • 正常模式:优先自旋,兼顾吞吐量

  • 饥饿模式:防止长等待协程饿死

  • 原子操作:确保状态变更的原子性

实际开发中应结合场景选择同步原语(如RWMutexsync.Mapatomic),并通过go test -race检测竞态条件

二.几种常见的锁及其原理

1. Mutex(互斥锁)

功能:互斥锁用于保护共享资源,确保同一时刻只有一个goroutine可以访问该资源, 防止多个goroutine同时访问共享资源。

  • 它有两种状态:锁定(locked)和未锁定(unlocked)。

  • 当锁已被持有时,其他尝试获取锁的goroutine会被阻塞,直到锁被释放。

  • 底层通过原子操作和信号量(sema)实现,结合了自旋和休眠等待机制,以提高性能

方法:

  • Lock(): 获取锁,如果锁已被占用,则调用者会被阻塞直到锁可用

  • Unlock(): 释放锁

底层结构

Go 复制代码
type Mutex struct {
    state int32  // 复合状态(锁标志/饥饿模式/等待计数)
    sema  uint32 // 信号量
}
  • 工作模式

    • 正常模式:新协程尝试自旋(约4次)抢锁,失败后入队

    • 饥饿模式(等待超1ms触发):新协程直接入队,锁按FIFO顺序移交

  • 原子操作 :通过atomic.CompareAndSwapInt32实现无锁状态变更

账户安全转账

Go 复制代码
type BankAccount struct {
    balance int
    mu      sync.Mutex
}

func (acc *BankAccount) Transfer(to *BankAccount, amount int) {
    acc.mu.Lock()
    defer acc.mu.Unlock()
    
    to.mu.Lock()
    defer to.mu.Unlock()
    
    acc.balance -= amount
    to.balance += amount
}

使用场景:保护共享数据的读写操作(如计数器、配置信息), 当有多个goroutine需要读写同一个共享变量或数据结构时,使用互斥锁来避免数据竞争

示例:多个goroutine并发更新一个计数器

复制代码
package main

import (
	"fmt"
	"sync"
)

type Counter struct {
	mu    sync.Mutex
	count int
}

func (c *Counter) Increment() {
	c.mu.Lock()         // 加锁
	defer c.mu.Unlock() // 函数结束时解锁
	c.count++
}

func main() {
	counter := Counter{}
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter.Increment()
		}()
	}

	wg.Wait()
	fmt.Println(counter.count) // 输出1000(无竞争)
}
Go 复制代码
// 从Prometheus中获取对应的 数据
func getSlotsPrometheusGamePool0(GameIdBet []*backstage.GameIdBet) ([]*backstage.SlotsPoolIndexCurModel, error) {
	curlPrometheus := server.FM.RemoteConfig.Setting["gameonline"]
	//curlPrometheus = "192.168.1.1"
	if curlPrometheus == "" {
		return nil, fmt.Errorf("Prometheus服务地址未配置")
	}
	// 准备通过协程并发查询所需数据
	var (
		results []*backstage.SlotsPoolIndexCurModel // 最终结果集
		wg      sync.WaitGroup                      // 等待组控制协程
		mu      sync.Mutex                          // 互斥锁保证线程安全
		errChan = make(chan error, len(GameIdBet))  // 错误通道
	)

	// 遍历所有ID和档位组合
	for _, gb := range GameIdBet {
		wg.Add(1) // 每个任务+1
		// 启动协程并发查询
		go func(gameId int32) {
			defer wg.Done() // 确保协程结束时通知等待组
			val, err := GetServiceGamePoolValue(curlPrometheus, gameId)
			if err != nil {
				errChan <- fmt.Errorf("机台ID:%d 查询失败: %v", gameId, err)
				return
			}
			// 线程安全地添加结果
			mu.Lock()
			results = append(results, &backstage.SlotsPoolIndexCurModel{
				GameId:    uint64(gameId),
				PoolValue: val,
			})
			mu.Unlock()
		}(gb.GameId)
	}

	// 等待所有协程完成
	wg.Wait()
	close(errChan) // 关闭错误通道

	// 检查是否有错误发生
	for err := range errChan {
		if err != nil {
			return results, fmt.Errorf("查询过程中发生错误: %v", err)
		}
	}

	// 返回最终结果
	return results, nil
}

2. RWMutex(读写锁)

功能:读写锁允许多个读操作同时进行,但写操作时完全互斥(写时不能读,也不能同时有多个写)

  • 当有写锁时,所有读锁和写锁都会被阻塞。

  • 当有读锁时,写锁会被阻塞,但读锁可以继续获取。

  • 底层通过两个互斥锁和一个计数器实现:一个互斥锁用于写操作,另一个用于读操作,计数器记录当前读操作的数量

方法:

  • Lock()Unlock():写锁

  • RLock()RUnlock():读锁

底层结构

Go 复制代码
type RWMutex struct {
    w           Mutex   // 写锁
    writerSem   uint32  // 写等待信号量
    readerSem   uint32  // 读等待信号量
    readerCount int32   // 当前读协程数
    readerWait  int32   // 等待读完成数
}
  • 读锁:多个协程可同时获取

  • 写锁:独占锁,获取时阻塞所有读写操作

  • 优先级:写锁优先(防止读锁饿死写锁)

使用场景:读多写少的场景(如缓存系统)。

Go 复制代码
type Config struct {
	data map[string]string
	rw   sync.RWMutex // 读写锁
}


// 高频读操作
func (c *Config) Get(key string) string {
	c.rw.RLock()         // 读锁定(允许多个读)
	defer c.rw.RUnlock()
	return c.data[key]
}

// 低频写操作
func (c *Config) Set(key, value string) {
	c.rw.Lock()         // 写锁定(独占)
	defer c.rw.Unlock()
	c.data[key] = value
}

func main() {
     cfg := Config{data: make(map[string]string)}

     // 启动多个读goroutine
     for i := 0; i < 10; i++ {
         go func() {
             for {
                 val := cfg.Get("key")
                 fmt.Println("Read:", val)
                 time.Sleep(10 * time.Millisecond)
             }
         }()
     }

     // 启动一个写goroutine
     go func() {
         count := 0
         for {
             cfg.Set("key", fmt.Sprintf("value%d", count))
             count++
             time.Sleep(100 * time.Millisecond)
         }
     }()

     time.Sleep(time.Second)
}
Go 复制代码
package main

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

var (
	data   map[string]string
	rwLock sync.RWMutex
)

func readData(key string) string {
	rwLock.RLock()
	defer rwLock.RUnlock()
	return data[key]
}

func writeData(key, value string) {
	rwLock.Lock()
	defer rwLock.Unlock()
	data[key] = value
}

func main() {
	data = make(map[string]string)
	// 写数据
	go func() {
		for i := 0; i < 5; i++ {
			writeData(fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i))
			time.Sleep(100 * time.Millisecond)
		}
	}()

	// 读数据
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < 5; j++ {
				val := readData(fmt.Sprintf("key%d", j))
				fmt.Printf("Goroutine %d: key%d -> %s\n", id, j, val)
				time.Sleep(50 * time.Millisecond)
			}
		}(i)
	}
	wg.Wait()
}
Go 复制代码
// 监控服务
type MonitorWarnService struct {
	db            *gorm.DB
	mu            sync.RWMutex
	monitors      map[string]*MonitorWarn // key: "gameId_Id"
	alertChan     chan *AlertWarnMessage
	ctx           context.Context
	cancel        context.CancelFunc
	checkInterval time.Duration
	// 添加飞书机器人配置
	feishuRobotConfig *appConfig.FeishuRobotConfig
}

//告警监控器
type MonitorWarn struct {
	Id         uint32    // 配置Id
	GameId     uint32    // 玩法id
	Id      uint32    // 房间id
	GameName   string    // 玩法名称
	WarnValue  int64     // 告警阈值
	WarnTime   uint32    // 告警频率
	LastAlert  time.Time // 上次告警时间
	AlertCount int       // 告警次数


// 加载状态为1(进行中)的监控配置
func (m *MonitorWarnService) loadActiveMonitors() error {
	var warnConfigs []model.WarnConfig
	if err := m.db.Where("status = ?", uint32(backstage.WarnStatus_WARN_STATUS_NORMAL)).Find(&warnConfigs).Error; err != nil {
		return err
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	// 清空现有监控器
	m.monitors = make(map[string]*MonitorWarn)

	// 添加新的监控配置
	for _, config := range warnConfigs {
		key := m.getMonitorKey(config.GameId, config.BetId)
		m.monitors[key] = &MonitorFishWarn{
			Id:        config.Id,
			GameId:    config.GameId,
			Id:     config.Id,
			GameName:  config.GameName,
			WarnValue: config.WarnValue,
			WarnTime:  config.WarnTime,
		}
		mylog.Infof("加载告警监控配置: 配置Id: %d, 玩法: %s, GameId: %d, BetId: %d, 告警阈值: %d, 告警频率: %d分钟/次", config.Id, config.GameName, config.GameId, config.Id, config.WarnValue, config.WarnTime)
	}

	return nil
}

3. WaitGroup(等待组)

功能:用于等待一组goroutine完成。主goroutine通过Add设置等待的goroutine数量,每个goroutine完成后调用Done,Wait会阻塞直到所有goroutine完成。

方法:

  • Add(delta int): 增加等待的goroutine数量,delta可为负数(但通常为正)。

  • Done(): 等同于Add(-1)。

  • Wait(): 阻塞直到计数器归零

使用场景:主goroutine需要等待所有子任务结束。

复制代码
func main() {
	var wg sync.WaitGroup
	urls := []string{"url1", "url2", "url3"}

	for _, url := range urls {
		wg.Add(1) // 增加计数器
		go func(u string) {
			defer wg.Done() // 任务完成时计数器减1
			fmt.Println("Fetching:", u)
		}(url)
	}

	wg.Wait() // 阻塞直到计数器归零
	fmt.Println("All tasks done")
}

4. Once(单次执行)

功能:确保某个操作在并发环境中只执行一次(如初始化),无论有多少个goroutine调用,无论有多少个goroutine调用且所有调用Once.Do的goroutine都会等待该操作执行完成。

  • 内部使用一个互斥锁和一个布尔标志,当第一次调用Do方法时,执行传入的函数并将标志置为已执行,后续调用将不再执行
Go 复制代码
type Once struct {
    done uint32 // 完成标志
    m    Mutex  // 互斥锁
}

func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
        o.doSlow(f)
    }
}

func (o *Once) doSlow(f func()) {
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}
  • 双检查机制:原子操作+互斥锁保证函数只执行一次

  • 内存屏障atomic.StoreUint32确保写入可见性

使用场景:单例模式或延迟初始化。

Go 复制代码
package main

import (
     "fmt"
     "sync"
)

type Singleton struct {
     // 假设这里有一些字段
}

var (
     instance *Singleton
     once     sync.Once
) 

func GetInstance() *Singleton {
	once.Do(func() {
		instance = &Singleton{} // 仅执行一次
	})
	return instance
}

func main() {
     var wg sync.WaitGroup
     for i := 0; i < 10; i++ {
         wg.Add(1)
         go func() {
             ins := GetInstance()
             fmt.Printf("%p\n", ins) // 打印的地址都相同
             wg.Done()
         }()
     }
     wg.Wait()
}

线程安全的单例初始化

Go 复制代码
var (
    instance *Service
    once     sync.Once
)

func GetService() *Service {
    once.Do(func() {
        instance = &Service{endpoint: "https://api.example.com"}
    })
    return instance
}
Go 复制代码
package main

import (
	"fmt"
	"sync"
)

var once sync.Once

func setup() {
	fmt.Println("Initialization done.")
}

func main() {
	for i := 0; i < 5; i++ {
		go func() {
			once.Do(setup)
		}()
	}
	// 等待goroutine执行
	time.Sleep(1 * time.Second)
}
// 输出: Initialization done. (只输出一次)
Go 复制代码
var (
	globalMonitor     *MonitorWarnService
	globalMonitorOnce sync.Once
	globalMonitorMu   sync.RWMutex
)

// 启动Slots告警监控服务
func InitMonitorWarnService(db *gorm.DB) {
	// 判断是否启用飞书告警服务
	service := &MonitorWarnService{}
	robotConfig, err := service.getWarnRobotConfig()
	if err != nil {
		panic(fmt.Sprintf("获取飞书机器人配置失败: %v", err))
	}

	// 检查机器人是否启用
	if !robotConfig.IsEnable {
		mylog.Infof("Slots游戏水池告警的飞书机器人未启用, 跳过发送告警")
		return
	}
	globalMonitorOnce.Do(func() {
		// 创建监控服务
		monitorService := NewMonitorWarnService(db, time.Duration(robotConfig.CheckInterval)*time.Second, robotConfig)

		// 启动监控服务
		if err := monitorService.Start(); err != nil {
			panic(fmt.Sprintf("Slots告警监控服务启动失败: %v", err))
		}

		// 设置全局实例
		SetMonitorSlotsWarn(monitorService)

		mylog.Infof("Slots告警监控系统已启动")

		// 在单独的goroutine中等待中断信号,不阻塞主程序
		go func() {
			sigChan := make(chan os.Signal, 1)
			signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

			<-sigChan
			mylog.Infof("收到停止信号,正在关闭监控服务...")

			monitorService.Stop()
			mylog.Infof("监控服务已正常退出")

			// 退出程序
			os.Exit(0)
		}()
	})
}
Go 复制代码
	wg := sync.WaitGroup{}
	wg.Add(9)

	go func() {
		defer wg.Done()
        // 逻辑代码
    }()
    wg.Wait()

5. Cond(条件变量)

功能:条件变量用于在多个goroutine之间进行通知,常与互斥锁配合使用。当某个条件不满足时,goroutine会进入等待状态,直到另一个goroutine修改条件并发出通知。

创建sync.NewCond(l Locker),其中l通常是一个Mutex或RWMutex。

方法:

  • Wait(): 释放锁并挂起当前goroutine,等待通知。被唤醒后会重新获取锁。

  • Signal(): 唤醒一个等待的goroutine。

  • Broadcast(): 唤醒所有等待的goroutine。

使用模式:需配合互斥锁和条件检查

Go 复制代码
type Cond struct {
    L Lock // 关联的锁(通常是Mutex)
    // 其他私有字段
}

使用场景:生产者-消费者模型。

Go 复制代码
package main

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

func main() {
     var (
         m     sync.Mutex
         cond  = sync.NewCond(&m)
         queue []int
     )

     // 消费者
     for i := 0; i < 2; i++ {
         go func(id int) {
             for {
                 m.Lock()
                 for len(queue) == 0 {
                     cond.Wait() // 等待队列不为空
                 }
                 item := queue[0]
                 queue = queue[1:]
                 fmt.Printf("Consumer %d got %d\n", id, item)
                 m.Unlock()
             }
         }(i)
     }

     // 生产者
     go func() {
         for i := 0; ; i++ {
             m.Lock()
             queue = append(queue, i)
             cond.Signal() // 通知一个消费者
             m.Unlock()
             time.Sleep(100 * time.Millisecond)
         }
     }()

     time.Sleep(time.Second)
}
Go 复制代码
type TaskQueue struct {
    tasks []Task
    cond  *sync.Cond
}

func NewQueue() *TaskQueue {
    q := &TaskQueue{}
    q.cond = sync.NewCond(&sync.Mutex{})
    return q
}

// 生产者
func (q *TaskQueue) Add(task Task) {
    q.cond.L.Lock()
    defer q.cond.L.Unlock()
    q.tasks = append(q.tasks, task)
    q.cond.Signal() // 唤醒一个消费者
}

// 消费者
func (q *TaskQueue) Pop() Task {
    q.cond.L.Lock()
    defer q.cond.L.Unlock()
    
    for len(q.tasks) == 0 {
        q.cond.Wait() // 释放锁并等待
    }
    
    task := q.tasks[0]
    q.tasks = q.tasks[1:]
    return task
}

6. Pool(对象池)

功能:用于存储和复用临时对象,减少内存分配和GC压力

**注意:**Pool中的对象可能会随时被回收。

方法

  • Get() interface{}: 从池中取出一个对象,如果池为空则调用New函数(如果设置了New字段)创建一个。

  • Put(x interface{}): 将对象放回池中

使用场景:高频创建/销毁临时对象的场景(如网络连接池,缓冲区(bytes.Buffer))。

Go 复制代码
var bufPool = sync.Pool{
	New: func() interface{} {
		return new(bytes.Buffer) // 创建新对象
	},
}

func Process(data string) {
	buf := bufPool.Get().(*bytes.Buffer) // 从池中获取
	defer bufPool.Put(buf)               // 放回池中
	buf.Reset()
	buf.WriteString(data)
	fmt.Println(buf.String())
}

func main() {
     Process('hello')
}

7. Map(并发安全映射)

功能并发安全的map,适用于读多写少或者多个goroutine操作的键集合不相交(即每个键只被一个goroutine写)的情况。比使用Mutex+Map的组合在特定场景下性能更好。

  • 内部通过读写分离和原子操作实现,避免使用锁导致性能下降。

  • 它通过两个映射(一个只读,一个可写)和原子操作来保证并发安全。

方法:

  • Load(key interface{}) (value interface{}, ok bool)

  • Store(key, value interface{})

  • Delete(key interface{})

  • LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)

  • Range(f func(key, value interface{}) bool)

使用场景:高频读/低频写的键值存储。

示例:多个goroutine并发读写不同的键

复制代码
var safeMap sync.Map

func main() {
	safeMap.Store("key1", 100)  // 写操作
	value, _ := safeMap.Load("key1") // 读操作
	fmt.Println(value) // 输出100

	safeMap.Range(func(k, v interface{}) bool {
		fmt.Println(k, v) // 遍历所有键值
		return true
	})
}

8.atomic(原子操作)

功能sync/atomic包提供了原子操作,用于对基本类型(int32, int64, uint32, uint64, uintptr)进行原子读写

  • 原子操作通过使用CPU原子指令实现,无需加锁,性能更高

    • AddT:原子加减

    • CompareAndSwapT:CAS操作

    • LoadT/StoreT:原子读写

  • 适用于简单的计数器等场景

  • 无锁编程:适用于简单状态更新,避免锁开销

Go 复制代码
package main

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

var counter int64

func increment() {
	atomic.AddInt64(&counter, 1)
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}
	wg.Wait()
	fmt.Println("Counter:", counter) // 输出: Counter: 1000
}

无锁计数器

Go 复制代码
type Counter struct {
    value int64
}

func (c *Counter) Increment() {
    atomic.AddInt64(&c.value, 1)
}

func (c *Counter) Value() int64 {
    return atomic.LoadInt64(&c.value)
}

9.errgroup.Group

带错误传播的协程组

Go 复制代码
var g errgroup.Group
g.Go(func() error { /* 任务1 */ })
g.Go(func() error { /* 任务2 */ })
if err := g.Wait(); err != nil {
    // 处理首个错误
}

总结

组件 核心用途 典型场景
Mutex 互斥访问共享资源 计数器、配置更新
RWMutex 读写分离锁 读多写少的缓存
WaitGroup 等待一组任务完成 批量任务并行处理
Once 确保单次执行 单例初始化
Cond 条件等待和唤醒 生产者-消费者模型
Pool 对象复用减少GC开销 网络连接池、缓冲区管理
Map 线程安全的键值存储 高频读取的全局配置

正确使用sync包可以避免数据竞争和死锁问题,确保并发程序的安全性和性能。

相关推荐
俩娃妈教编程2 小时前
C++基础知识点:位运算
java·开发语言·jvm·c++·位运算
掘金者阿豪2 小时前
从“多库掣肘”到“一库平川”:金仓KingbaseES的融合数据库深度体验
后端
路弥行至2 小时前
linux运行脚本出现错误信息 /bin/bash^M: bad interpreter解决方法
linux·运维·开发语言·经验分享·笔记·其他·bash
一直不明飞行2 小时前
C++ pari使用的两个注意事项
开发语言·c++
wefly20172 小时前
无需安装的 M3U8 在线播放器,快速实现 HLS 流预览与调试
java·开发语言·python·开发工具
飞Link2 小时前
深度解析:建模动作序列(Action Sequence Modeling)的实战指南
开发语言·python·数据挖掘
CoderCodingNo2 小时前
【GESP】C++六级/五级练习题 luogu-P1323 删数问题
开发语言·c++·算法
We་ct2 小时前
LeetCode 211. 添加与搜索单词 - 数据结构设计:字典树+DFS解法详解
开发语言·前端·数据结构·算法·leetcode·typescript·深度优先
呆萌很2 小时前
【GO】类型转换练习题
golang