【muduo】base库之 Atomic

一、原子操作的概念

       原子操作指的是由多步操作组成的一个操作。如果该操作不能原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集

       所谓的原子操作,取的就是“原子是最小的、不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而效率更高。原子操作不需要加锁
 

      任何要求多于一个函数调用的操作都不是原子操作,因为在两个函数调用之间,内核可能会临时挂起线程,执行其他的操作,当内核切换回当前线程时,之前的数据可能别修改,所以不能保证是原子操作。

 

二、为什么需要原子操作

C++中针对共享数据的存取在并发条件下可能会引起数据竞争的未定义行为,需要限制并发程序以某种特定的顺序执行。

举个例子:我们知道,count++这种操作不是原子的,如果是多线程程序需要使用全局计数器,程序就需要使用锁或者互斥量,对于较高并发的程序,会造成一定的性能瓶颈。

一个自加操作,本质是分成三步的:

  1. 从内存取到寄存器
  2. 在寄存器加1
  3. 存入内存。

 由于时序的因素,多个线程操作同一个全局变量,会出现问题,如图所示:

可以看到,最后得到的 x=11,但是实际我们希望得到的 x是12。这也是并发编程的难点。在目前多核条件下,这种困境会越来越彰显出来。最简单的处理办法就是加锁保护,但原子性操作可以做到比互斥锁更小的开销

原子性操作会把  load eax,x;  add eax,1; store x,eax看做一个整体。

现代CPU一般都提供了相关原子性操作的指令,在gcc编译器中提供了如下的原子操作API

//n++类
type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)


//++n类
type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)

这两组函数的区别在于第一组返回更新前的值,第二组返回更新后的值。type可以是1,2,4或8字节长度的int类型;

后面的可扩展参数(...)用来指出哪些变量需要memory barrier,因为目前gcc实现的是full barrier,,所以可以略掉这个参数。

 

//CAS类
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

/* 对应的伪代码 */
{if(*ptr==oldval){*ptr=newval;return true;}else{return false;}}
{if(*ptr==oldval){*ptr=newval;}return oldval;}

这两个函数提供原子的比较和交换(CAS操作),如果*ptr == oldval,就将newval写入*ptr,如果*ptr 不等于oldval,这意味着另一个并发进程已经成功地竞争到 CAS并成功将 ptr的值从 oldval更改为别的值了。
第一个函数在相等并写入的情况下返回true.
第二个函数在返回操作之前的值

 

使用这些原子性操作,编译的时候需要加 -march = cpu-type

 

muduo中的AtomicIntegerT 模板类将几个gcc中的原子操作方法封装了起来,用以维护一个可能要被多个线程访问读写的变量

 

三、Atomic.h源代码

// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#ifndef MUDUO_BASE_ATOMIC_H
#define MUDUO_BASE_ATOMIC_H

#include "muduo/base/noncopyable.h"

#include <stdint.h>

namespace muduo
{

namespace detail
{
template<typename T>
// noncopyable类中实现了:禁止默认拷贝构造函数和赋值运算符
class AtomicIntegerT : 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()
  {
    // in gcc >= 4.7: __atomic_load_n(&value_, __ATOMIC_SEQ_CST)
	//比较value的值是否等于0:如果等于0就将value的值设置为0,返回的是修改之前的value的值;
	//如果不等于0,就不进行设置操作,返回的也是修改之前的value的值。
	//所以就相当于获取当前value的值,并且是原子性操作,是线程安全的。
    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)
	//返回未修改过的value的值,然后将value加上x,是原子性操作
    return __sync_fetch_and_add(&value_, x);
  }

  T addAndGet(T x)
  {
	//获取了修改之后的值
    return getAndAdd(x) + x;
  }

  //自增1操作 ++n(先加后获取)
  T incrementAndGet()
  {
    return addAndGet(1);
  }

  //自减1操作 --n(先减后获取)
  T decrementAndGet()
  {
    return addAndGet(-1);
  }
  
  //仅将value增加了x,不返回
  void add(T x)
  {
    getAndAdd(x);
  }
  
  //仅将value增加1,不返回
  void increment()
  {
    incrementAndGet();
  }

  //仅将value减少1,不返回
  void decrement()
  {
    decrementAndGet();
  }

  T getAndSet(T newValue)
  {
    // in gcc >= 4.7: __atomic_exchange_n(&value, newValue, __ATOMIC_SEQ_CST)
	//将value_设为newValue,并返回value_操作之前的值。
    return __sync_lock_test_and_set(&value_, newValue);
  }

 private:
  volatile T value_;   //防止value_被优化
};
}  // namespace detail

typedef detail::AtomicIntegerT<int32_t> AtomicInt32; //64位原子性整数类
typedef detail::AtomicIntegerT<int64_t> AtomicInt64;

}  // namespace muduo

#endif  // MUDUO_BASE_ATOMIC_H

 

四、volatile关键字详解

volatile的作用:作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile 关键字对多线程编程很重要,想获取的值可能被其它的线程更改过,如果还是从寄存器中获取,就无法得到正确的值。

计算机中内存、cache和寄存器之间的关系及区别

  • 寄存器是中央处理器(CPU)内的组成部份。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,包含的寄存器有累加器(ACC)。
  • 内存包含的范围非常广,一般分为只读存储器(ROM)、随机存储器(RAM)和高速缓存存储器(cache)。
  • Cache :即高速缓冲存储器,是位于CPU与主内存间的一种容量较小但速度很高的存储器。由于CPU的速度远高于主内存,CPU直接从内存中存取数据要等待一定时间周期,Cache中保存着CPU刚用过或循环使用的一部分数据,当CPU再次使用该部分数据时可从Cache中直接调用,这样就减少了CPU的等待时间,提高了系统的效率。Cache又分为一级Cache(L1 Cache)和二级Cache(L2 Cache),L1 Cache集成在CPU内部,L2 Cache早期一般是焊在主板上,现在也都集成在CPU内部,常见的容量有256KB或512KB L2 Cache。
     

大致来说数据是通过内存-Cache-寄存器,Cache缓存则是为了弥补CPU与内存之间运算速度的差异而设置的的部件。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潇湘夜雨~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值