(11)muduo_base库源码分析:原子性

文章目录

    • 1.为什么需要原子性操作
    • 2.Atomic.h代码分析
    • 3.原子性操作可以实现无锁队列
    • 4.muduo的编译选项
    • 5.Types.h的研究

1.为什么需要原子性操作

  • x++;
    (1)从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x所处的内存地址
    (2)我们希望x的值被2个线程加2次,最后等于12,但是却等于11
    (11)muduo_base库源码分析:原子性_第1张图片
  • 解决原子问题方法1:使用锁
    当一个线程进入这个区域的时间,另一个线程就不能访问的该区域的资源,必须等到另外一个线程unlock,方可进入,但会引起锁竞争问题
lock
x++
unlock
  • 解决原子问题方法2:使用原子操作
    (1)将下面看成一个整体
    (11)muduo_base库源码分析:原子性_第2张图片
    (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

2.Atomic.h代码分析

  • 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 

namespace muduo
{

namespace detail
{
template<typename T>//T表示传递进来的类型
class AtomicIntegerT : noncopyable //表示AtomicIntegerT类型是不可以拷贝的,就是将=运算符做成私有的
{
 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,就将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的值,再加x
    return __sync_fetch_and_add(&value_, x);
  }

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

  T incrementAndGet()
  {
    //自增,先加后获取
    return addAndGet(1);
  }

  T decrementAndGet()
  {
    //自减,先减后获取
    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

//模板实例化
typedef detail::AtomicIntegerT<int32_t> AtomicInt32;//32bit的原子性整数类
typedef detail::AtomicIntegerT<int64_t> AtomicInt64;

}  // namespace muduo

#endif  // MUDUO_BASE_ATOMIC_H

  • 测试代码目录:
    (1)11\jmuduo\muduo\base\tests\Atomic_unittest.cc 来自于muduo\base\tests\Atomic_unittest.cc
    (2)11\jmuduo\muduo\base\tests\CMakeLists.txt来来自10的Timestamp的研究
    (3)11\jmuduo\muduo\base\Atomic.h来自于muduo\base\Atomic.h
    (11)muduo_base库源码分析:原子性_第3张图片
==============================11\jmuduo\muduo\base\tests\CMakeLists.txt=========================================
add_executable(timestamp_unittest Timestamp_unittest.cc)
target_link_libraries(timestamp_unittest muduo_base)

add_executable(atomic_unittest Atomic_unittest.cc)
#target_link_libraries(atomic_unittest muduo_base)##这个例子只提供了头文件,不链接muduo_base库也可以

  • 编译和执行情况
位置参考:
jiwangreal@ubuntu:~/wangji/src/11/build/debug/bin$ ./atomic_unittest

结果啥都没有,因为assert都是真,所以没啥输出

在这里插入图片描述

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

  • 无锁队列的链表实现
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

4.muduo的编译选项

  • eg:11\jmuduo\CMakeLists.txt
    (11)muduo_base库源码分析:原子性_第4张图片
-Wall			// 大部分警告 
-Wextra			// 一些额外的警告
-Werror			// 当出现警告时转为错误,停止编译
-Wconversion		// 一些可能改变值的隐式转换,给出警告。
-Wno-unused-parameter	// 函数中出现未使用的参数,不给出警告。
-Wold-style-cast		// C风格的转换,给出警告
-Woverloaded-virtual	// 如果函数的声明隐藏住了基类的虚函数,就给出警告。
-Wpointer-arith		// 对函数指针或者void *类型的指针进行算术操作时给出警告,若没有警告,就是地址+1
-Wshadow		// 当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。
-Wwrite-strings		// 规定字符串常量的类型是const char[length],因此,把这样的地址复制给 non-const char *指针将产生警告.这些警告能够帮助你在编译期间发现企图写入字符串常量 的代码
-march=native		// 指定cpu体系结构为本地平台
-D_FILE_OFFSET_BITS=64  //定义一个宏
  • eg:
#include 

void foo(int x)
{
}

class B
{
public:
	virtual void foo()
	{
	}
};

class D : public B
{
public:
	void foo(int x)//派生类隐藏住了基类的虚函数,编译选项加上-Woverloaded-virtual就会显示这类警告
	{
	}
};


int main(void)
{
	int n;
	double d = 1.23;
	n = d;
	return 0;
}

5.Types.h的研究

  • from:muduo\base\Types.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);
}

  • eg:
#include 

void foo(int x)
{
}

class B
{
public:
	virtual void foo()
	{
	}
};

class D : public B
{
public:
	void foo(int x)//派生类隐藏住了基类的虚函数,编译选项加上-Woverloaded-virtual就会显示这类警告
	{
	}
};

template<typename To, typename From>
inline To implicit_cast(From const &f) {
  return f;
}

int main(void)
{
	int n;
	double d = 1.23;
	n = d;

	B* pb;
	D* pd = NULL;

	pb = pd;

	//pd隐式转化成了pb,对比上面的写法而言,下面的更加清楚
	pb = implicit_cast<B*, D*>(pd);//这里测试 -Wconversion		// 一些可能改变值的隐式转换,给出警告,这里就不会给出警告
	return 0;
}

你可能感兴趣的:(开源代码学习)