mutex 与 CAS

来源:互联网 发布:windows解压war包 编辑:程序博客网 时间:2024/06/06 18:43

CAS: compare and swap

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

template <class T>  bool CAS(T* addr, T expected, T value) {     if (*addr == expected) {        *addr = value;        return true;     }     return false;  } 

mutex的一种实现cas

bool i = 0;//1表示锁住lock(){    cas(i, 0, 1)}unlock(){    cas(i, 1, 0);}

mutex的一种实现sac(sac是这样叫的吗?我自己想的)

首先要知道,每个线程都有它自己的一组CPU寄存器,称为线程的上下文。该上下文反映了线程上次运行时该线程的CPU寄存器的状态。
假设Mutex变量的值为1表示互斥锁空闲,这时某个进程调用lock可以获得锁,而Mutex的值为0表示互斥锁已经被某个线程获得,其它线程再调用lock只能挂起等待。那么lock和unlock的伪代码如下:

mutex = 0;lock:       movb $1, %al       xchgb %al, mutex       if(al寄存器的内容 == 0){           return 0;       } else           挂起等待;       goto lock;  unlock:       movb $0, mutex       唤醒等待Mutex的线程;       return 0;  

想起来std::atomic类模版中有这样一个成员函数exchange(),跟这个有点相似,把它包装一下就可以实现一个mutex(因为exchange可以保证在任意时刻,atomic中的数只有一个线程能得到)。

// 给它起个名叫1000000米赛跑#include <iostream>       // std::cout#include <atomic>         // std::atomic#include <thread>         // std::thread#include <vector>         // std::vectorstd::atomic<bool> ready (false);std::atomic<bool> winner (false);void count1m (int id) {  while (!ready) {}                  // wait for the ready signal  for (int i=0; i<1000000; ++i) {}   // go!, count to 1 million  if (!winner.exchange(true)) { std::cout << "thread #" << id << " won!\n"; }};int main (){  std::vector<std::thread> threads;  std::cout << "spawning 10 threads that count to 1 million...\n";  for (int i=1; i<=10; ++i) threads.push_back(std::thread(count1m,i));  ready = true;  for (auto& th : threads) th.join();  return 0;}

http://www.cplusplus.com/reference/atomic/atomic/exchange/

貌似现在的mutex有优化?lock会首先在应用层上检测冲突,如果有冲突也会继续try几次,如果还无法取得锁才陷入休眠状态。

关于自旋锁:

自旋锁,当无法获得锁时,不会陷入内核休眠,而是执行空循环。最好的情况应该是 拥有锁的线程要很快执行完,即等待线程空循环等待的时间小于线程切换的时间,否则自旋锁没有任何优势。
尤其是,假设并发稍微多点,线程1在lock之后unlock之前发生了时钟中断,一段时间后才会被切回来调用unlock,那么这段时间中另一个调用lock的线程就得空跑while了。这才是最浪费cpu时间的地方。
内核的话,可以开关中断,可以让持有锁的线程不切换出去,从而尽快的执行完,所以自旋锁适合在内核某一些情况下使用;如果在用户态使用,那么结果的好坏就要依靠操作系统的线程调度了,有点“靠天吃饭”的感觉。

关于递归锁:
看着很有意义,但是建议是不去用。recursive mutex 可能会隐藏代码里的一些问题。典型情况是你以为拿到一个锁就能修改对象了,没想到外层代码已经拿到了锁,正在修改(或读取)同一个对象呢。http://blog.csdn.net/Solstice/article/details/5307710#_Toc2810
而用普通锁则不会发生这种暗地里偷偷修改的事情,它会死锁,提醒你修改自己的函数逻辑。