单goroutine通过select+多通道读写map是否线程安全?
会话管理器的线程安全性问题
我正为Web应用实现会话管理器,采用单goroutine管理内存中的session map以避免数据库请求。目前尚未完成完整实现,想先了解该方案为何线程安全?
实现代码如下:
type Session struct { Id uuid.UUID `json:"sessionId"` Email string `json:"email"` CreatedAt time.Time `json:"createdAt"` } type SessionManager struct { loadBulk chan uint8 // buffered channel load chan Session get chan uuid.UUID resultGet chan Session remove chan uuid.UUID } func (sm SessionManager) LoadBulk() { sm.loadBulk <- 1 } func (sm SessionManager) Load(s Session) { sm.load <- s } func (sm SessionManager) Get(id uuid.UUID) Session { sm.get <- id return <-sm.resultGet } func (sm SessionManager) Remove(id uuid.UUID) { sm.remove <- id } func NewSessionManager() *SessionManager { sessionManager := SessionManager{ loadBulk: make(chan uint8, 1), load: make(chan Session), get: make(chan uuid.UUID), resultGet: make(chan Session), remove: make(chan uuid.UUID), } sessions := make(map[uuid.UUID]Session) // this map will be read/write go func() { for { select { case <-sessionManager.loadBulk: log.Info().Msg("Load session bulk get all non expired from database") case session := <-sessionManager.load: log.Info().Msg("Load session " + fmt.Sprintf("%v", session.Id)) case id := <-sessionManager.get: log.Info().Msg("Get session " + fmt.Sprintf("%v", id)) sessionManager.resultGet <- Session{} case id := <-sessionManager.remove: log.Info().Msg("Remove session " + fmt.Sprintf("%v", id)) case <-time.After(30 * time.Second): log.Info().Msg("Remove expired sessions from map and database") } } }() return &sessionManager }
该SessionManager仅会有一个实例,当服务器收到多个请求并同时调用其方法读写map时,是否会出现线程安全问题?原因是什么?
结论:不会出现线程安全问题,这个方案本身就是线程安全的
- 所有对session map的操作被限制在单个goroutine内:你定义的
sessionsmap仅在启动的后台goroutine中进行读写,处理HTTP请求的其他goroutine不会直接操作这个map,从根源上避免了多goroutine并发读写的可能。 - channel的同步特性保证操作串行化:外部goroutine调用
Load/Get/Remove等方法时,本质是将操作请求发送到对应的channel中,后台goroutine通过select语句依次处理这些请求——同一时间只会执行一个map操作,不存在竞争条件。 - Go语言的channel本身具备线程安全性:多个goroutine同时向channel发送数据时,channel会自动保证发送操作的串行化,不会出现数据竞争或混乱。
补充:当前代码尚未实现对sessions map的实际读写逻辑(比如Get方法仅返回空Session),但只要后续所有map的读写操作都放在这个后台goroutine中执行,就能始终保持线程安全。
内容的提问来源于stack exchange,提问作者Nico
相关产品推荐
相关产品推荐

