英文:
Store 2 values in one variable
问题
这是Go代码正确且可移植的吗?我需要将两个计数器(每次调用只更新一个计数器)存储在一个变量中,以避免在实际代码中使用单个atomic.AddUint64()时锁定整个结构体。
package main
import "fmt"
var long uint64 // 实际计数器存储
func main() {
left := uint32(100) // 第一个计数器
right := uint32(200) // 第二个计数器
long = uint64(left)
long = long << 32 | uint64(right)
fmt.Println(left, right)
long += uint64(1 << 32) // 增加左计数器
long += 1 // 增加右计数器
xLeft := uint32(long >> 32) // 获取左计数器
xRight := uint32(long) // 获取右计数器
fmt.Println(xLeft, xRight)
}
你可以在这里查看代码:http://play.golang.org/p/aBlp-Zatgn
英文:
Is this Go code correct and portable, I need to store 2 counters (each call only one counter will be updated) in one variable to avoid locks in actual code where I am going to use single atomic.AddUint64() instead of locking whole struct.
<!-- language: lang-golang -->
package main
import "fmt"
var long uint64 // Actual counters storage
func main() {
left := uint32(100) // First counter
right := uint32(200) // Second counter
long = uint64(left)
long = long << 32 | uint64(right)
fmt.Println(left, right)
long += uint64(1 << 32) // Increment left
long += 1 // Increment right
xLeft := uint32(long >> 32) // Get left
xRight := uint32(long) // Get right
fmt.Println(xLeft, xRight)
}
答案1
得分: 0
这段Go代码是正确的,只要你使用64位宽度的无符号整数。
在这种情况下,通过sync/atomic
包提供了对Go编译器支持的体系结构的可移植性。然而,请注意,并非所有体系结构都支持对64位宽度数据进行“真正”的原子操作。例如,i386实现使用了CAS循环:
TEXT ·AddUint64(SB),NOSPLIT,$0-20
// 没有XADDQ,所以使用CMPXCHG8B循环
MOVL addr+0(FP), BP
TESTL $7, BP
JZ 2(PC)
MOVL 0, AX // crash with nil ptr deref
// DI:SI = delta
MOVL delta_lo+4(FP), SI
MOVL delta_hi+8(FP), DI
// DX:AX = *addr
MOVL 0(BP), AX
MOVL 4(BP), DX
addloop:
// CX:BX = DX:AX (*addr) + DI:SI (delta)
MOVL AX, BX
MOVL DX, CX
ADDL SI, BX
ADCL DI, CX
// if *addr == DX:AX {
// *addr = CX:BX
// } else {
// DX:AX = *addr
// }
// all in one instruction
LOCK
CMPXCHG8B 0(BP)
JNZ addloop
// success
// return CX:BX
MOVL BX, new_lo+12(FP)
MOVL CX, new_hi+16(FP)
RET
这可能会引发一个问题:为什么不使用带有锁的结构体?
编辑以回答评论中的问题:是的,使用32位整数将在所有Go支持的体系结构上实现实际的原子操作,因为据我所知,它们都支持XADDL
(或类似操作)。
英文:
> Is this Go code correct and portable
It is correct, as long as you work with unsigned integers of 64-bit width.
Portability in this case is provided by the sync/atomic
packages to the architectures supported by the Go compiler. Note, however, that not all architectures support "true" atomic operations on 64-bit wide data. For example, the i386 implementation uses a CAS-loop:
TEXT ·AddUint64(SB),NOSPLIT,$0-20
// no XADDQ so use CMPXCHG8B loop
MOVL addr+0(FP), BP
TESTL $7, BP
JZ 2(PC)
MOVL 0, AX // crash with nil ptr deref
// DI:SI = delta
MOVL delta_lo+4(FP), SI
MOVL delta_hi+8(FP), DI
// DX:AX = *addr
MOVL 0(BP), AX
MOVL 4(BP), DX
addloop:
// CX:BX = DX:AX (*addr) + DI:SI (delta)
MOVL AX, BX
MOVL DX, CX
ADDL SI, BX
ADCL DI, CX
// if *addr == DX:AX {
// *addr = CX:BX
// } else {
// DX:AX = *addr
// }
// all in one instruction
LOCK
CMPXCHG8B 0(BP)
JNZ addloop
// success
// return CX:BX
MOVL BX, new_lo+12(FP)
MOVL CX, new_hi+16(FP)
RET
That may open the question: why not use a struct with a lock?
Edit to answer question in comments: Yes, using a 32-bit integer would result in actual atomic operations on all Go-supported architectures because they all support XADDL
(or analog) to the best of my knowledge.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论