0
点赞
收藏
分享

微信扫一扫

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


文章目录

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

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

  • x++;
    (1)从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x所处的内存地址
    (2)我们希望x的值被2个线程加2次,最后等于12,但是却等于11
  • 解决原子问题方法1:使用锁
    当一个线程进入这个区域的时间,另一个线程就不能访问的该区域的资源,必须等到另外一个线程unlock,方可进入,但会引起锁竞争问题

lock
x++
unlock

  • 解决原子问题方法2:使用原子操作
    (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

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 <stdint.h>

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\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都是真,所以没啥输出

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

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 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。
    即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存
    对于多线程很重要!!!
  • 参考:​​无锁队列的实现​​

4.muduo的编译选项

  • eg:11\jmuduo\CMakeLists.txt

-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 <stdio.h>

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 <stdio.h>

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;
}


举报

相关推荐

0 条评论