Error

C++博客 首页 新随笔 联系 聚合 管理
  217 Posts :: 61 Stories :: 32 Comments :: 0 Trackbacks
先看下boost给的例子,我觉得有问题:

#include
<boost/atomic.hpp> class spinlock { private: typedef enum {Locked, Unlocked} LockState; boost::atomic<LockState> state_; public: spinlock() : state_(Unlocked) {} void lock() {
// 可能这里的boost::memory_order_acquire有原子操作的效果吧,偶不是很理解,不过我觉得这里应该用cae操作才对 while (state_.exchange(Locked, boost::memory_order_acquire) == Locked) { /* busy-wait */ } } void unlock() {
// 这里都直接写不做检查了,更加难以理解 state_.store(Unlocked, boost::memory_order_release); } };

有可能是我不理解后面的内存访问参数的意义,给下我自己的实现:
class CESpinLock : boost::noncopyable
{
private:
    typedef 
enum {emUnlocked = 0, emLocked} EM_LockState;

public:
    CESpinLock() : m_atomicState(emLocked)
    {
    }

public:
    
void lock()
    {
        EM_LockState state 
= emUnlocked;
        
while(false == m_atomicState.compare_exchange_strong(state, emLocked))
        {
            state 
= emUnlocked;
        }
    }

    
void unlock()
    {
        EM_LockState state 
= emLocked;
        
while(false == m_atomicState.compare_exchange_strong(state, emUnlocked))
        {
            state 
= emLocked;
        }
    }

private:
    boost::atomic
<EM_LockState> m_atomicState;
};


可以适当的在false里边加一点sleep操作感觉。

还有一点就是不太激烈这里的cae操作分两种 strong和weak

bool compare_exchange_weak(T & expected, T desired, memory_order success_order, memory_order failure_order)

Compare current value with expected, change it to desired if matches. Returns true if an exchange has been performed, and always writes the previous value back in expected. May fail spuriously, so must generally be retried in a loop.

bool compare_exchange_strong(T & expected, T desired, memory_order order)

Compare current value with expected, change it to desired if matches. Returns true if an exchange has been performed, and always writes the previous value back in expected

实在不理解 May fail spuriously, so must generally be retried in a loop.的意义,不过看了代码,在win32的实现上,weak是调用了stong实现的。



 VCZH.粉丝数组[0]<errorcpp@qq.com>  21:49:07
atomic的 compare_exchange_weak
compare_exchange_weak
有啥区别

求解释
vczh.Iskandar<vczh@163.com>  21:49:27
不是一样吗
御虚舟北(314969051)  21:49:40
改代码中, ing
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:49:49
Windows上的实现是一样的
May fail spuriously, so must generally be retried in a loop. 
这一句怎么理解呢
vczh.Iskandar<vczh@163.com>  21:50:07
compare_exchange_weak
compare_exchange_weak
质量最大vczh粉(402740419)  21:50:14
compare_exchange_weak
compare_exchange_weak

VCZH.粉丝数组[0]<errorcpp@qq.com>  21:50:16
strong

compare_exchange_strong
还有一个问题
class spinlock {
private:
  typedef enum {Locked, Unlocked} LockState;
  boost::atomic<LockState> state_;

public:
  spinlock() : state_(Unlocked) {}

  void lock()
  {
    while (state_.exchange(Locked, boost::memory_order_acquire) == Locked) {
      /* busy-wait */
    }
  }
  void unlock()
  {
    state_.store(Unlocked, boost::memory_order_release);
  }
};

boost例子给的 spinloc
怎么是这样实现的
都没有用cae操作
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:51:20
unlock都直接用store了
vczh.Iskandar<vczh@163.com>  21:51:50
不用compare
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:51:59
 为啥
无法理解
vczh.Iskandar<vczh@163.com>  21:52:34
想要解释好麻烦
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:52:40
还有在Windows上
boost::memory_order_acquire
这个参数也没用
貌似
求V神解释
还有strong和weak的区别
质量最大vczh粉(402740419)  21:54:46
spinlock本来就不用compare啊
直接swap就行了
while (state_.swap(1) == 1);
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:56:24
你看假设现在是lock状态
boost的实现是无条件吧lock换成unlock
如果是继续lock 他还是lock
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:58:08
只要要避免 重入吧
lock之前检查一下
御虚舟北(314969051)  22:00:03
小康你的书收到没有
质量最大vczh粉(402740419)  22:03:17
VCZH.粉丝数组[0]<errorcpp@qq.com>  21:58:08
只要要避免 重入吧
lock之前检查一下

你用错了他不管你
就是这个意思
同一个线程lock两次也会死锁
VCZH.粉丝数组[0]<errorcpp@qq.com>  22:05:05
原来是这样

但是他lock的时候不检查,也会导致两个线程同时lock吧?
while (state_.exchange(Locked, boost::memory_order_acquire) == Locked) {
      /* busy-wait */
    }

质量最大vczh粉(402740419)  22:05:18
不会啊
假设A进程先来了,lock成功了
VCZH.粉丝数组[0]<errorcpp@qq.com>  22:05:33
怎能理解,我理解和直接赋值是一样
我再去看
质量最大vczh粉(402740419)  22:05:40
这不是复制
是exchange
swap
另一个线程exchange就会收到Locked
那么另一个线程就会while循环,直到原来线程给设置了Unlocked
VCZH.粉丝数组[0]<errorcpp@qq.com>  22:06:47
Exchange current value with new_value, returning current value 

exchange是把新值写入旧值返回么?  不是这样么?
我有点理解了
质量最大vczh粉(402740419)  22:07:46
对啊,新值写入,旧值返回,原子的
VCZH.粉丝数组[0]<errorcpp@qq.com>  22:07:59
就是说写入也是写入的lock, 不影响之前的lock
当前线程拿到旧值检查是不是lock状态,如果是就继续尝试直到不是
质量最大vczh粉(402740419)  22:08:00
所以只会有一个线程返回Unlocked,另一个线程会收到之前线程设置的Locked
VCZH.粉丝数组[0]<errorcpp@qq.com>  22:08:11
 受教了
质量最大vczh粉(402740419)  22:08:13

VCZH.粉丝数组[0]<errorcpp@qq.com>  22:08:20
我贴到博客上去 

posted on 2013-03-31 21:49 Enic 阅读(3764) 评论(1)  编辑 收藏 引用 所属分类: cpp 1x and boost

评论

# re: boost::atomic 实现 spinlock 2013-03-31 23:52 Enic
老外是这么说的:

http://cbloomrants.blogspot.be/2011/07/07-14-11-compareexchangestrong-vs.html


07-14-11 - compare_exchange_strong vs compare_exchange_weak

The C++0x standard was revised a while ago to split compare_exchange (aka CAS) into two ops. A quick note on the difference :

bool compare_exchange_weak( T * ptr, T * old, T new );

bool compare_exchange_strong( T * ptr, T * old, T new );

(BTW in the standard "old" is actually a reference, which is a damn shitty thing to do because it makes it very non-transparent that "old" gets mutated by these functions, so I am showing it as a pointer).
both try to do :


atomically {
if ( *ptr == *old ) { *ptr = new; return true; }
else { *old = *ptr; return false; }
}

the difference is that compare_exchange_weak can also return false for spurious failure. (the original C++0x definition of CAS always allowed spurious failure; the new thing is the _strong version which doesn't).
If it returns due to spurious failure, then *old might be left untouched (and in fact, *ptr might be equal to *old but we failed anyway).

If spurious failure can only occur due to contention, then you can still gaurantee progress. In fact in the real world, I believe that LL-SC architectures cannot gaurantee progress, because you can get spurious failure if there is contention anywhere on the cache line, and you need that contention to be specifically on your atomic variable to gaurantee progress. (I guess if you are really worried about this, then you should ensure that atomic variables are padded so they get their own cache line, which is generally good practice for performance reasons anyway).

On "cache line lock" type architectures like x86, there is no such thing as spurious failure. compare_exchange just maps to "cmpxchg" instruction and you always get the swap that you want. (it can still fail of course, if the value was not equal to the old value, but it will reload old). (BTW it's likely that x86 will move away from this in the future, because it's very expensive for very high core counts)

compare_exchange_weak exists for LL-SC (load linked/store conditional) type architectures (Power, ARM, basically everything except x86), because on them compare_exchange_strong must be implemented as a loop, while compare_exchange_weak can be non-looping. For example :

On ARM, compare_exchange_weak is something like :

compare_exchange_weak:

ldrex // load with reservation
teq // test equality
strexeq // store if equal
and strexeq can fail for two reasons - either because they weren't equal, or because the reservation was lost (because someone else touched our cache line).
To implement compare_exchange_strong you need a loop :

compare_exchange_strong:

while ( ! compare_exchange_weak(ptr,old,new) ) { }

(note that you might be tempted to put a (*old = *ptr) inside the loop, but that's probably not a good idea, and not necessary, because compare_exchange_weak will eventually load *ptr into *old itself when it doesn't fail spuriously).
The funny bit is that when you use compare_exchange you often loop anyway. For example say I want to use compare_exchange_strong to increment a value, I have to do :


cur = *ptr;
while( ! compare_exchange_strong(ptr,&cur,cur+1) ) { }

(note it's a little subtle that this works - when compare_exchange_strong fails, it's because somebody else touched *ptr, so we then reload cur (this is why "old" is passed by address), so you then recompute cur+1 from the new value; so with the compare_exchange_strong, cur has a different value each time around this loop.)
But on an LL-SC architecture like ARM this becomes a loop on a loop, which is dumb when you could get the same result with a single loop :


cur = *ptr;
while( ! compare_exchange_weak(ptr,&cur,cur+1) ) { }

Note that with this loop now cur does *not* always take a new value each time around the loop (it does when it fails due to contention, but not when it fails just due to reservation-lost), but the end result is the same.
So that's why compare_exchange_weak exists, but you might ask why compare_exchange_strong exists. If we always use loops like this, then there's no need for it. But we don't always use loops like this, or we might want to loop at the much higher level. For example you might have something like :

bool SpinLock_TryLock(int * lock)
{
int zero = 0;
return compare_exchange_strong(lock,&zero,1);
}
which returns false if it couldn't get the lock (and then might do an OS wait) - you don't want to return false just because of a spurious failure. (that's not a great example, maybe I'll think of a better one later).
(BTW I think the C++0x stuff is a little bit broken, like most of C standardization, because they are trying to straddle this middle ground of exposing the efficient hardware-specific ways of doing things, but they don't actually expose enough to map directly to the hardware, and they also aren't high level enough to separate you from knowing about the hardware. For example none of their memory model actually maps directly to what x86 provides, therefore there are some very efficient x86-specific ways to do threading ops that cannot be expressed portable in C++0x. Similarly on LL-SC architectures, it would be preferrable to just have access to LL-SC directly.

I'd rather see things in the standard like "if LL-SC exist on this architecture, then they can be invoked via __ll() and __sc()" ; more generally I wish C had more conditionals built into the language, that would be so much better for real portability, as opposed to the current mess where they pretend that the language is portable but it actually isn't so you have to create your own mess of meta-language through #defines).  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理