高并发服务器架构笔记(3)——muduo_base 源码分析
来源:互联网 发布:熊猫加速器mac 编辑:程序博客网 时间:2024/06/07 12:43
Timestamp 类
- Timestamp 类封装
- < base/types.h >
- less_than_comparable
- 要求实现 <,可自动实现 > , <=, >=
- Boost_STATIC_ASSERT
- 使用 PRId64
- Timestamp 实现及测试
class Timestamp : public muduo::copyable, public boost::less_than_comparable<Timestamp>{//...}
- C++ 变量的两种语义
- 值语义:可已拷贝的,拷贝之后,与原对象脱离关系。
- 对象语义:
- 要么不能拷贝
- 要么可以拷贝,但拷贝之后与原对象仍然存在一定的关系,比如共享底层资源。需要自己实现拷贝构造函数。
muduo::copyable
空基类,标识类,值类型,表示可复制。- microSecondsSinceEpoch_ 64位整形,表示距离 1970-01-01 的微秒数。
- BOOST_STATIC_ASSERT 编译时断言,ASSERT 运行时断言。分别在编译时和运行时报错。
PRId64 是为了跨平台打印int64_t
- 如果不用这个宏的话
printf("%ld", value);
// 64 位系统printf("%lld", value);
// 32 位系统
- 使用这个宏可跨平台
#define __STDC_FORMAT_MACROS#include <inttypes.h>#undef __STDC_FORMAT_MACROSprintf("%" PRId64, "\n", value);
- 如果不用这个宏的话
原子性操作
- 为什么需要原子性操作
- x++
- 从内存中读 x 的值到寄存器中,对寄存器加 1, 再把新值写回 x 所处的内存地址。
假设两个线程分别执行 x++
显然是不符合常理的
- gcc 提供的原子性操作
// 原子自增操作// *ptr + valuetype __sync_fetch_and_add (type *ptr, type value)// 原子比较和交换(设置)操作type __sync_val_compare_and_swap (type *ptr, type oldval type newval)bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval)// 原子赋值操作type __sync_lock_test_and_set (type *ptr, type value)// 使用这些原子性操作,编译的时候需要加-march=cpu-type
如果没有这些原子性操作,我们就要用锁,锁操作比原子性操作的开销大得多。
AtomicIntegerT 类封装
class AtomicIntegerT : boost::noncopyable{}
- volatile:作为指令关键字,确保本条指令不会因为编译器的优化而省略,且要求每次直接读值。简单的说就是防止编译器对代码进行优化。
- 当使用 volatile 声明的变量的时候,系统总是重新从他所在的内存中读取数据。即使他前面的指令刚刚从该处读取过数据。而且读取的数据立即被保存。
- 编译选项
-Wall // 大部分警告
-Wextra // 一些额外的警告
-Werror // 当出现警告时转为错误,停止编译
-Wconversion // 一些可能改变值的隐式转换,给出警告。
-Wno-unused-parameter // 函数中出现未使用的参数,不给出警告。
-Wold-style-cast // C风格的转换,给出警告
-Woverloaded-virtual // 如果函数的声明隐藏住了基类的虚函数,就给出警告。
-Wpointer-arith // 对函数指针或者void *类型的指针进行算术操作时给出警告
-Wshadow // 当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告。
-Wwrite-strings // 规定字符串常量的类型是const char[length],因此,把这样的地址复制给 non-const char *指针将产生警告.这些警告能够帮助你在编译期间发现企图写入字符串常量 的代码
-march=native // 指定cpu体系结构为本地平台
Exception类
- Exception 类实现
- backtrace 栈回溯,保存各个帧栈的地址
- backtrace_symbols,根据地址,转成相应的函数符号
- abi::__cxa_demangle
Thread 类
- Linux 下的 POSIX 线程也有一个id,类型是 pthread_t,由 pthread_self() 取得,该 id 由线程库维护。其 id 空间是由各个进程独立的(即不同进程中的线程可能有相同的 id)。Linux 中的 POSIX 线程库实现的线程其实也是一个进程(LWP 轻量级进程),只是该进程与主进程(启动线程的进程)共享一些资源,比如代码段,数据段等。
- 有些时候我们需要知道线程的真实pid,比如说进程 P1 要向另一个进程 P2 中的某个线程发送信号,既不能使用 P2 的 pid,更不能使用线程的 pthread_id,而只能使用该线程的真实 pid,称为 tid。
gettid()
可以得到 tid,但glibc 并没有实现该函数,只能通过 Linux 的系调用syscall
来获取。return syscall(SYS_gettid)
muduo 中 Thread 类图(采用基于对象风格)
typedef boost::function< void () > ThreadFunc
与线程进程有关的知识
线程标识符
- phread_self
- gettid
__thread
,gcc内置的线程局部存储设施,这种变量每个线程都有一份。- __thread 只能修饰 POD 类型
- POD 类型(plain old data),与 C 兼容的原始数据。如结构体和整形等 C 语言中的类型是 POD 类型,但带有用户定义的构造函数或虚函数的类则不是。
__thread string t_obj1("cppcourse"); // 错误,不能调用类的构造函数__thread string8 t_obj2 = new string; // 错误,初始化只能是编译期常量__thread string t_obj3 = NULL; //正确
namespace muduo{ namespace CurrentThread { __thread 修饰的变量是线程局部存储的 __thread int t_cacheTid = 0; // 线程 tid 的缓存,减少::syscall(SYS_gettid) 系统调用的次数,提高效率。 __thread char t_tidString[32];// tid 的字符串表示形式 __thread const chat* t_threadName = "unknown"; // 每个线程名称 const bool sameType = boost::is_same<int, pid_t>::value; // boost::is_same 判断是否相同类型 BOOST_STATIC_ASSERT(sameType); // 编译时断言 }}
- 如果想把非 POD 类型设为线程特定数据,即每个线程都有一份,我们可以用 TSD (线程特定数据)实现。
boost::is_same
判定两种类型是否同一种类型pthread_atfork(void(*prepare)(void), void(*parent)(void), void(*child)(void))
- 调用
fork
时,内部创建子进程前在父进程中会调用 prepare, 内部创建子进程成功后,父进程会调用 parent, 子进程会调用 child。 fork
可能是在主线程中调用,也可能是在子线程中调用。fork
得到一个新进程,新进程只有一个执行序列,只有一个线程(调用 fork 的线程被继承下来)。- 对于编写多线程程序来说,我们最好不要再调用
fork
。不要编写多线程多进程 程序, 要么用多线程,要么用多进程。 - 一个死锁的实例
- 调用
// 一个在多线程程序里fork造成死锁的例子// 一个输出示例:/*pid = 19445 Entering main ...pid = 19445 begin doit ...pid = 19447 begin doit ...pid = 19445 end doit ...pid = 19445 Exiting main ...父进程在创建了一个线程,并对mutex加锁,父进程创建一个子进程,在子进程中调用doit,由于子进程会复制父进程的内存,这时候mutex处于锁的状态,父进程在复制子进程的时候,只会复制当前线程的执行状态,其它线程不会复制。因此子进程会处于死锁的状态。*/#include <stdio.h>#include <time.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* doit(void* arg){ printf("pid = %d begin doit ...\n",static_cast<int>(getpid())); pthread_mutex_lock(&mutex); struct timespec ts = {2, 0}; nanosleep(&ts, NULL); pthread_mutex_unlock(&mutex); printf("pid = %d end doit ...\n",static_cast<int>(getpid())); return NULL;}int main(void){ printf("pid = %d Entering main ...\n", static_cast<int>(getpid())); pthread_t tid; pthread_create(&tid, NULL, doit, NULL); struct timespec ts = {1, 0}; nanosleep(&ts, NULL); if (fork() == 0) { doit(NULL); } pthread_join(tid, NULL); printf("pid = %d Exiting main ...\n",static_cast<int>(getpid())); return 0;}
fork()
的时候,mutex 已经上了锁,然后再次调用doit()
,再次尝试上锁,造成了死锁。尽管原来的进程中的 mutex 在 2s 后解锁,但子进程不会复制该解锁状态(因为复制的时候没有解锁)。
- pthread_atfork() 就可用于解决这类问题。
- 把 mutex 的解锁代码放在
prepare()
中 - 把 mutex 再次上锁的代码放在
parent()
中
- 把 mutex 的解锁代码放在
阅读全文
0 0
- 高并发服务器架构笔记(3)——muduo_base 源码分析
- 高并发服务器架构笔记(3)——muduo_base 源码分析
- 高并发服务器架构笔记(4)——muduo_net 源码分析
- 11muduo_base库源码分析(二)
- 12muduo_base库源码分析(三)
- 13muduo_base库源码分析(四)
- 15muduo_base库源码分析(六)
- 16muduo_base库源码分析(七)
- 17muduo_base库源码分析(八)
- 18muduo_base库源码分析(九)
- 19muduo_base库源码分析(十)
- 20muduo_base库源码分析(十一)
- muduo_base 源码分析:Timestamp
- muduo_base 源码分析:AtomicIntegerT
- 高并发服务器架构--SEDA架构分析
- 高并发服务器架构笔记(1)——poll 和 epoll
- 高并发服务器架构笔记(2)——面向对象编程风格
- 高并发服务器架构
- mysql学生成绩排名,分组取前 N 条记录
- 软件分层架构理解【小白专用】
- 使用freemarker生成复杂的excel表格
- 从零开始写javaweb框架笔记13-搭建轻量级JAVAWEB框架-开发一个类加载器
- 九度1028:继续畅通工程
- 高并发服务器架构笔记(3)——muduo_base 源码分析
- 根据多个索引高效删除python list中对应位置的元素
- pyhton 数据预处理 数据读取与存储 csv
- Struts 2基础入门
- 【个人笔记重点,不作为参考】主题:restify搭建模拟RESTful API
- MySQL数据库知识点总结
- Oracle12C--批量操作(三十四)
- Java接口和Java抽象类的存在价值
- 搜索专题 F