muduo源码分析之Atomic原子性操作

相关文件

muduo/base/Atomic.h //AtomicIntegerT类封装
muduo/base/tests/Atomic_unitest.cc //测试示例

作用

保证线程安全。
为什么需要原子性操作?
如两个线程同时执行x++,需要先从内存读x的值到寄存器中,对寄存器加一,再把新值写回x所处的内存地址。可见这个过程是有可能被打断的。
image
如果两线程同时执行x++,有可能就像图一个结果x只增加了1。
原子性操作保证一个线程对变量操作时其他线程不能对这个变量操作。

使用

直接看muduo提供得测试示例。
执行该示例没有任何输出,因为断言都通过了。

//muduo/base/tests/Atomic_unitest.cc
#include "muduo/base/Atomic.h"
#include <assert.h>

int main()
{
  {
  muduo::AtomicInt64 a0; //64位整数
  assert(a0.get() == 0); //取值
  assert(a0.getAndAdd(1) == 0);//先取后加1
  assert(a0.get() == 1);//变为1
  assert(a0.addAndGet(2) == 3);//先加后取值,等于3
  assert(a0.get() == 3);
  assert(a0.incrementAndGet() == 4);//加1后取值
  assert(a0.get() == 4);
  a0.increment();
  assert(a0.get() == 5);
  assert(a0.addAndGet(-3) == 2);
  assert(a0.getAndSet(100) == 2);
  assert(a0.get() == 100);
  }

  {
  muduo::AtomicInt32 a1; //32位整数
  assert(a1.get() == 0);
  assert(a1.getAndAdd(1) == 0);
  assert(a1.get() == 1);
  assert(a1.addAndGet(2) == 3);
  assert(a1.get() == 3);
  assert(a1.incrementAndGet() == 4);
  assert(a1.get() == 4);
  a1.increment();
  assert(a1.get() == 5);
  assert(a1.addAndGet(-3) == 2);
  assert(a1.getAndSet(100) == 2);
  assert(a1.get() == 100);
  }
}

其中AtomicInt64和AtomicInt32是模板类AtomicIntegerT的类模板。

//muduo/base/Atomic.h
typedef detail::AtomicIntegerT<int32_t> AtomicInt32;//32位整数
typedef detail::AtomicIntegerT<int64_t> AtomicInt64;

AtomicIntegerT源码分析

gcc 原子操作

一般为保证原子性操作,会使用mutex加锁,而锁竞争会影响服务器的性能,应该尽可能减少锁的使用。
而muduo使用的是gcc提供的原子操作,开销比锁小,更加高效,前提是cpu支持相关的指令。

// 原子自增操作
type __sync_fetch_and_add (type *ptr, type value)

// 原子比较和交换(设置)操作
//在写入新值之前, 读出旧值, 当且仅当旧值与存储中的当前值一致时,才把新值写入存储
type __sync_val_compare_and_swap (type *ptr, type oldval type newval)
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval)

// 原子赋值操作
type __sync_lock_test_and_set (type *ptr, type value)

使用这些原子性操作,编译的时候选项需要加-march=cpu-type,一般可写成-march=native,让cpu自行检测其架构。

AtomicIntegerT模板类

类图
image

namespace detail
{
template<typename T>
class AtomicIntegerT : noncopyable
{
 public:
 //构造
  AtomicIntegerT()
    : value_(0)
  {
  }

  T get() //取值
  {
    // in gcc >= 4.7: __atomic_load_n(&value_, __ATOMIC_SEQ_CST)
	//先比较并设置value,返回value的旧值
	//等于0,则设置成0,
	//不等于0,则不赋值
    return __sync_val_compare_and_swap(&value_, 0, 0);
  }

  T getAndAdd(T x)//先获取后加
  {
    // in gcc >= 4.7: __atomic_fetch_add(&value_, x, __ATOMIC_SEQ_CST)
    return __sync_fetch_and_add(&value_, x);
  }

  T addAndGet(T x)//先加后获取
  {
    return getAndAdd(x) + x;
  }

  T incrementAndGet()//自增1
  {
    return addAndGet(1);
  }

  T decrementAndGet()//减1
  {
    return addAndGet(-1);
  }

  void add(T x)
  {
    getAndAdd(x);
  }

  void increment()
  {
    incrementAndGet();
  }

  void decrement()
  {
    decrementAndGet();
  }

  T getAndSet(T newValue)
  {
    // in gcc >= 4.7: __atomic_exchange_n(&value_, newValue, __ATOMIC_SEQ_CST)
    return __sync_lock_test_and_set(&value_, newValue);
  }

 private:
  volatile T value_; //值
};
}  // namespace detail

volatile修饰符

可看到上面类模板中值value前面使用了volatile修饰符
volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化。
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

posted @ 2021-06-06 19:56  零十  阅读(218)  评论(0编辑  收藏  举报