(11)muduo_base库源码分析:原子性:为什么需要原子性操作 ,Atomic.h代码分析 ,原子性操作可以实现无锁队列 ,muduo的编译选项 ,Types.h的研究
原创
©著作权归作者所有:来自51CTO博客作者喜欢打篮球的普通人的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
- 1.为什么需要原子性操作
- 2.Atomic.h代码分析
- 3.原子性操作可以实现无锁队列
- 4.muduo的编译选项
- 5.Types.h的研究
1.为什么需要原子性操作
- x++;
(1)从内存中读x的值到寄存器中,对寄存器加1,再把新值写回x所处的内存地址
(2)我们希望x的值被2个线程加2次,最后等于12,但是却等于11 - 解决原子问题方法1:使用锁
当一个线程进入这个区域的时间,另一个线程就不能访问的该区域的资源,必须等到另外一个线程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代码分析
// 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都是真,所以没啥输出
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 //定义一个宏
#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的研究
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);
}
#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;
}