登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go atomic.AndUint64 怎样原子清除状态位

来源:17golang原创

时间:2026-11-01 15:55:49 275浏览 收藏

要原子清除 uint64 状态字中的某几位,关键不是把待清除位直接传给 atomic.AndUint64,而是先取反得到保留掩码:keepMask := ^bitsToClear。然后执行 oldState := atomic.AndUint64(&state, keepMask)。这样按位 AND 会把目标位变成 0,同时保留其他位;返回值 oldState 是更新前的状态快照。

官方文档:https://pkg.go.dev/sync/atomic#AndUint64

如果要清除的状态位是 bitsToClear,就传入 ^bitsToClear。不要直接传待清除位,否则保留下来的位会被一起清零。

先把状态位和反掩码分清

可以把一个 uint64 看成一组并发状态标志。例如最低位表示在线,第二位表示有错误:

package main

import "sync/atomic"

const (
	// 用独立的位表示不同状态,便于组合成一个状态字。
	stateOnline uint64 = 1 

这里的 stateError 是“要清除的位”,^stateError 才是“要保留的位”。因为 AND 0 的结果一定是 0,AND 1 才能保持原值。

Go uint64 状态字、待清除状态位、反掩码与 atomic.AndUint64 地址的静态关系
图1:状态字、待清除位和反掩码的静态关系图,不是运行截图或验证结果。

调用 AndUint64 并读取旧状态

AndUint64 的签名是 func AndUint64(addr *uint64, mask uint64) (old uint64)。它对 *addr 做原子按位 AND,并返回旧值。返回旧值很有用:它能让调用方在一次原子更新中判断“清除之前”某个标志是否存在。

func clearFlags(state *uint64, bitsToClear uint64) (oldState uint64, wasSet bool) {
	// 反掩码中的 1 表示保留,0 表示清除对应状态位。
	keepMask := ^bitsToClear
	// oldState 是更新前快照,不是清除后的新状态。
	oldState = atomic.AndUint64(state, keepMask)
	// 只从旧快照判断目标位,避免并发更新改变判断依据。
	wasSet = oldState&bitsToClear != 0
	return oldState, wasSet
}

例如 bitsToClear 同时包含在线位和错误位时,两个目标位都会被清零,其余 62 位保持原值。wasSet 只说明清除前至少有一个目标位为 1,不代表清除后状态字仍然包含它们。

Go atomic.AndUint64 的状态地址、保留掩码、旧状态和清除前标志判断关系
图2:AndUint64 的输入、旧状态和调用方判断关系图,展示接口职责,不是运行流程截图。

把位清除封装成稳定的状态接口

业务代码不要到处手写取反表达式,可以把位定义和清除动作放在同一个类型附近。这样调用方只传“想撤销的状态”,不容易误把保留掩码传错。

type StateWord uint64

func (s *StateWord) Clear(bits uint64) (old StateWord) {
	// bits 表示调用方希望清除的位,方法内部负责转换为保留掩码。
	old = StateWord(atomic.AndUint64((*uint64)(s), ^bits))
	return old
}

func (s StateWord) Has(bits uint64) bool {
	// 读取快照时使用值接收者;复合状态判断仍需由更高层设计同步边界。
	return uint64(s)&bits != 0
}

这个封装解决的是位掩码表达的一致性,不会自动把多个普通字段读写变成事务,也不会替代锁。若业务需要“检查多个条件后再决定是否更新”,应重新审视整个状态转换是否需要 CAS 或互斥保护。

64 位对齐和 atomic.Uint64 的选择

官方文档提醒,在 ARM、386 和 32 位 MIPS 等平台上,通过旧式原子函数访问 64 位字时,调用方需要负责安排 64 位对齐;把 uint64 放在结构体中时尤其要留意布局。使用类型封装通常更清楚:

type State struct {
	// atomic.Uint64 自带适合原子访问的类型语义,且不能在首次使用后复制。
	flags atomic.Uint64
}

func (s *State) Clear(bits uint64) (old uint64) {
	// Uint64.And 与 AndUint64 的掩码规则相同:传入要保留的位。
	return s.flags.And(^bits)
}

atomic.Uint64.And 适合新代码,因为状态字段本身表达了“这是原子 uint64”,也减少了手动取地址和布局误用的机会。若需要兼容既有的 *uint64 API,atomic.AndUint64 仍然可以使用,但应把对齐和复制规则写进类型约定。

常见误区和速查表

场景正确做法原因
清除某些位AndUint64(addr, ^bitsToClear)目标位与 0 做 AND,其他位与 1 做 AND
判断清除前是否开启检查返回的 oldState&bitsToClear返回值是原子更新前的快照
新增原子状态字段优先考虑 atomic.Uint64类型语义和对齐边界更明确
复合条件更新评估 CAS 或锁一次 AND 只保证这次位更新原子

相关问题

直接传 bitsToClear 会怎样?

会把待清除位之外的大多数位清零,因为按位 AND 的 0 会清除对应位置。清除状态位时必须传反掩码。

返回值是更新后的状态吗?

不是。AndUint64 返回的是更新前的旧值;如果需要新值,应根据已知掩码推导,或在明确的同步边界下再次原子读取。

AndUint64 能替代所有锁吗?

不能。它只适合单个 64 位状态字的原子按位更新;跨字段不变量、复合检查和多步状态迁移仍需要更完整的同步方案。

记住一句话:要清除哪些位,就把这些位取反后交给 AND;要判断清除前的状态,就读取函数返回的旧值。新代码可用 atomic.Uint64.And 表达同样语义,并把原子字段的类型边界写得更清楚。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>