Interlocked API的原子性如何保证
来源:互联网 发布:斐讯k2访客网络限速 编辑:程序博客网 时间:2024/06/10 07:19
前面的文章提到如何利用Interlocked API设计系统级日志。Interlocked API可以对在多线程之间共享的内存变量提供原子性访问。有些CPU在硬件层面上直接支持这些操作,如80386以后的X86架构CPU,xchg、xadd、cmpxchg等指令在进行内存访问时锁住总线。举例来说, InterlockedExchangeAdd在X86上的实现如下:
直接利用了xadd指令完成交换。
问题是,其他架构的CPU没有提供类似的指令可以锁定总线,Interlocked API的原子性如何保证?拿ARM架构来说,在Windows CE上InterlockedExchangeAdd的ARM实现如下:
翻译成C语言就是:
完全是一个普通的函数。在多线程环境下,这样的实现是不足以保证原子性的。举个例子,你有一个全局变量g_lVar,线程1和线程2会改变它的值:
在InterlockedExchange不能保证原子性的情况下会出现什么问题?正常情况下,两个线程执行完thread_entry函数后,g_lVar的值为2。现在设想一下这种情况:线程1执行完InterlockedExchangeAdd中的第一句(此时oldval为0),时间片刚好用完,线程调度器唤醒线程2,线程2执行顺利执行完thread_entry,g_lVar为1。线程调度器切换回到线程1执行,由于线程1中本地变量oldval为0,*Target=newval=oldval+Value=0+1=1,因此线程1的thread_entry完成后,g_lVar的值仍然为1!!问题处在InterlockedExchangeAdd的执行可能被别的线程打断,导致操作数有可能被其他线程改变,而InterlockedExchangeAdd本身无法预知这一点,也就是说InterlockedExchangeAdd的原子性无法得到保证。
Windows CE怎么解决这一问题?答案是调度器!在CPU被切换到另外一个context执行前(比如当前任务被中断打断,或者发生一个Data Abort)调度器会检查原先任务是否运行到Interlocked API所在的地址范围,如果是的话,调度器把指令寄存器修正为该Interlocked API的入口地址。在我们的例子中,InterlockedExchangeAdd执行完第一句时间片用完,调度器发现该线程的指令寄存器处于InterlockedExchangeAdd API之间,因此它会把指令寄存器重置回InterlockedExchangeAdd的入口地址,再切换到线程2执行。在线程2完成后线程1运行时,oldval的值重新从*Target(即g_lVar,此时已被线程2改为1)取得,这样,在线程1完成后g_lVar的值变成2。要注意的是,调度器不保证Interlocked API的执行不被打断,它保证的是Interlocked API的一次完整执行不会被其他任务打断!还要注意的是,在多处理器或多核系统上,这种方法是行不通的,必须在硬件层次上提过某种锁定总线的内存访问机制才行。
- LONG WINAPI InterlockedExchangeAdd(PLONG Addend, LONG Value)
- {
- __asm {
- mov ecx, Addend
- mov eax, Value
- xadd [ecx], eax
- }
- }
问题是,其他架构的CPU没有提供类似的指令可以锁定总线,Interlocked API的原子性如何保证?拿ARM架构来说,在Windows CE上InterlockedExchangeAdd的ARM实现如下:
- LEAF_ENTRY InterlockedExchangeAdd
- ldr r12, [r0]
- add r2, r12, r1
- str r2, [r0]
- mov r0, r12 ; (r0) = return original value
- bx lr
- ENTRY_END InterlockedExchangeAdd
- LONG InterlockedExchangeAdd(PLONG Addend, LONG Value)
- {
- LONG oldval = *Target; // ldr r12, [r0]
- LONG newval = oldval+Value; // add r2, r12, r1
- *Target = newval; // str r2, [r0]
- return oldval; // mov r0, r12; bx lr
- }
- LONG g_lVar = 0;
- void thread_entry()
- {
- LONG oldval = InterlockedExchangeAdd(&g_lVar, 1);
- }
Windows CE怎么解决这一问题?答案是调度器!在CPU被切换到另外一个context执行前(比如当前任务被中断打断,或者发生一个Data Abort)调度器会检查原先任务是否运行到Interlocked API所在的地址范围,如果是的话,调度器把指令寄存器修正为该Interlocked API的入口地址。在我们的例子中,InterlockedExchangeAdd执行完第一句时间片用完,调度器发现该线程的指令寄存器处于InterlockedExchangeAdd API之间,因此它会把指令寄存器重置回InterlockedExchangeAdd的入口地址,再切换到线程2执行。在线程2完成后线程1运行时,oldval的值重新从*Target(即g_lVar,此时已被线程2改为1)取得,这样,在线程1完成后g_lVar的值变成2。要注意的是,调度器不保证Interlocked API的执行不被打断,它保证的是Interlocked API的一次完整执行不会被其他任务打断!还要注意的是,在多处理器或多核系统上,这种方法是行不通的,必须在硬件层次上提过某种锁定总线的内存访问机制才行。
- Interlocked API的原子性如何保证
- C# 多线程Interlocked类保证原子操作
- linux kernel中如何保证append写的原子性
- linux kernel中如何保证append写的原子性
- HBase原子性保证
- HBase原子性保证
- 从JDK源码角度看java并发的原子性如何保证
- 原子操作Interlocked
- volatile不能保证原子性
- volatile 不能保证变量的原子性的操作
- volatile无法保证i++原子性的解决方案
- java——保证原子性操作的CAS算法
- 用户模式的线程同步原子访问:InterLocked互锁函数
- C#中的原子操作Interlocked,你真的了解吗?
- C#中的原子操作Interlocked你真的了解吗?
- volaile 保证可见性而非原子性,保证修饰变量即时变更需要依赖代码本身的原子性
- volatile不能保证程序执行的原子性以及只能一定程度上保证有序性
- volatile不能保证程序执行的原子性以及只能一定程度上保证有序性
- 用Live Writer找回你的BLOG的密码
- 网络程序服务端设计二
- VC实现工具栏的下拉箭头按钮
- Linux 编程之C与脚本的混合编程
- 十大开源软件
- Interlocked API的原子性如何保证
- 网络服务端设计三
- SELECT 表单跳转到制定的网页
- JavaScript教程--从入门到精通--(3)中错误的更改
- C# 如何理解委托事件(一)
- 网络游戏服务器端程序设计 四
- VS2005怎么样创建Global.asax文件
- 在db2中替换like的办法
- 实现拖放层的JS代码