muduo_base库源码分析(三):原子性:为什么需要原子性操作 ,Atomic.h代码分析 ,原子性操作可以实现无锁队列 ,muduo的编译选项 ,Types.h的研究

为什么需要原子操作

最最典型的例子:x++

我们知道它有三个步骤,1.从内存中读x的值到寄存器中 2.对寄存器加1 3.再把新值写回x所处的内存地址
在这里插入图片描述
假设x的初始值为0,我们使用两个线程对x++,我们期待x的值为3,但实际上可能为2。

原因是有可能多个处理器同时从各自的缓存中读取变量i,分别进行加一操作,然后分别写入系统内存当中。那么想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。

要想安全得到正确的答案:
1.使用互斥锁,但是多个线程访问势必出现锁竞争,锁竞争性能杀手之一,不推荐
2.使用原子性操作,这就是本文的核心,原子性,就是不可再分,把三个过程作为一个整体。

首先看看gcc自带的原子操作:

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


// 原子比较和交换(设置)操作,先比较,后交换(设置)
//若*ptr == oldval,则*ptr=newvl,并返回oldval
//返回bool类型,若*ptr == oldval,则返回为真,再设置;若比较失败,则返回false,也不会设置
type __sync_val_compare_and_swap (type *ptr, type oldval type newval)
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval)

// 原子赋值操作,*ptr=value
type __sync_lock_test_and_set (type *ptr, type value)
//一般设置native,让系统自动检测本地cpu的类型
使用这些原子性操作,编译的时候需要加-march=cpu-type

Atomic.h代码分析

#ifndef MUDUO_BASE_ATOMIC_H
#define MUDUO_BASE_ATOMIC_H
 
#include <boost/noncopyable.hpp>
#include <stdint.h>
 
namespace muduo
{
 
namespace detail
{
template<typename T>
class AtomicIntegerT : boost::noncopyable
{
 public:
  AtomicIntegerT()
    : value_(0)
  {
  }
 
  // uncomment if you need copying and assignment
  //
  // AtomicIntegerT(const AtomicIntegerT& that)
  //   : value_(that.get())
  // {}
  //
  // AtomicIntegerT& operator=(const AtomicIntegerT& that)
  // {
  //   getAndSet(that.get());
  //   return *this;
  // }
 
  T get()    //返回value_的值,如果value_=0,把它和0交换。
  {
    // in gcc >= 4.7: __atomic_load_n(&value_, __ATOMIC_SEQ_CST)
    return __sync_val_compare_and_swap(&value_, 0, 0);
  }
 
  T getAndAdd(T x)    //先获取没有修改的value_的值,再给value_+x
  {
    // in gcc >= 4.7: __atomic_fetch_add(&value_, x, __ATOMIC_SEQ_CST)
    return __sync_fetch_and_add(&value_, x);
  }
 
  T addAndGet(T x)    //先加,后获取,get_and_add + x相当于先加后获取
  {
    return getAndAdd(x) + x;
  }
 
  T incrementAndGet()    //先加1后获取
  {
    return addAndGet(1);
  }
 
  T decrementAndGet()  //先减1后获取
  {
    return addAndGet(-1);
  }
 
  void add(T x)   //加x,无返回值
  {
    getAndAdd(x);
  }
 
  void increment()    //自加1,无返回值
  {
    incrementAndGet();
  }
 
  void decrement()   //自减1,无返回值
  {
    decrementAndGet();
  }
 
  T getAndSet(T newValue)   //先get然后设置为新的值
  {
    // in gcc >= 4.7: __atomic_store_n(&value, newValue, __ATOMIC_SEQ_CST)
    return __sync_lock_test_and_set(&value_, newValue);
  }
 
 private:
  volatile T value_;   //使用volatile修饰,避免编译器优化。
};
}
 
typedef detail::AtomicIntegerT<int32_t> AtomicInt32;
typedef detail::AtomicIntegerT<int64_t> AtomicInt64;
}
 
#endif  // MUDUO_BASE_ATOMIC_H

原子性操作可以实现无锁队列

  • 无锁队列的链表实现
EnQueue(Q, data) //进队列
{
    //准备新加入的结点数据
    n = new node();
    n->value = data;
    n->next = NULL;
	
	//下面的p可以指向尾节点,也可以不指向尾节点
    do {
        p = Q->tail; //取链表尾指针的快照
    } while( CAS(p->next, NULL, n) != TRUE); 
    //while条件注释:如果没有把结点链在尾指针上,再试
	
	//若插入成功,Q->tail和p指针一定是相等的,置尾结点 Q->tail = n;
    CAS(Q->tail, p, n); 
}

说明:
(1)CAS是:原子比较与设置

if (p->next==null)
{
	p->netx=n;//p如果指向的是尾节点,就将新节点添加到链表尾部
	return TRUE;
}
else
	return FALSE;2)但是你会看到,为什么我们的“置尾结点”的操作(第13行)不判断是否成功,因为:
如果有一个线程T1,它的while中的CAS如果成功的话,那么其它所有的/随后线程的CAS都会失败(因为其它线程插入的都不是尾节点),然后就会再循环;
此时,如果T1 线程还没有更新tail指针,其它的线程继续失败,因为tail->next不是NULL;
直到T1线程更新完 tail 指针,于是其它的线程中的某个线程就可以得到新的 tail 指针,继续往下走了;

所以,只要线程能从 while 循环中退出来,意味着,它已经“独占”了,tail 指针必然可以被更新;

  • volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
    (1)简单地说就是防止编译器对代码进行优化
    (2)当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。
    即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存
    对于多线程很重要!!!

  • 参考

无锁队列的实现
https://coolshell.cn/articles/8239.html

Type.h的研究

template<typename To, typename From>
inline To implicit_cast(From const &f)//隐式转型函数
{
  return f;
}

template<typename To, typename From>     // use like this: down_cast<T*>(foo);
inline To down_cast(From* f)     //向下转换                // so we only accept pointers
{
  //永远不会满足下面的if
  if (false)
  {
    implicit_cast<From*, To>(0);
  }

//!defined(NDEBUG)表示是调试状态
//!defined(GOOGLE_PROTOBUF_NO_RTTI)表示开启运行时的类型识别
#if !defined(NDEBUG) && !defined(GOOGLE_PROTOBUF_NO_RTTI)
  assert(f == NULL || dynamic_cast<To>(f) != NULL);  // RTTI: debug mode only!
#endif
  return static_cast<To>(f);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值