谁能想到,这名16岁的少女四年前只是一名年仅12岁的少女
前言
注:个人水平有限,不能保证此文完全正确,如果错误,请不吝指出。
此文在观看《大并发服务器开发》视频时所做。
因此本文所讲基于muduo库0.9.1-beta版本,需要依赖cmake及boost.
(rawmd不依赖boost,依赖c++11)
sudo apt-get install cmake libboost-dev libboost-test-dev .更新时间:<2018-03-12>
原创时间: <2018-03-12>
面向对象编程与基于对象编程
std::boost::function
std::bootst::bind
muduo::base中类关系及作用
copyable.h 一个空类,用来标识子类是值语义。
Types.h 提供字符串类的声明,及两个类型转换函数模板。
- implicit_cast (隐式转换),使代码阅读时更明晰。
- down_cast (向下转型),基类指针转换为派生类指针,前提是基类指针指向派生类对象。
Timestamp.h/cc 提供封装好的时间类。
- 继承copyable,值语义。
- 继承boot::less_than_comparable
,less_than_comparable:要求提供<,可自动实现>,<=,>= - BOOST_STATIC_ASSERT(sizeof(Timestamp) == sizeof(int64_t)); 编译期间的assert,在编译时就可以报错。
- 打印64位整数使用 PRld64, printf(“%” PRld64 “n”, value_64); 可以做到跨平台(32和64位)。
Atomic.h 提供原子操作模板类。延伸阅读:无锁队列的实现
- 提供了AtomicInt32(用来标识线程数使用)和AtomicInt64(目前还未看出作用) 两个类。
- volatile关键字:确保本条指令不会因编译器的优化而省略,且要求每次直接读值。当要求使用volatile声明的变量的值时,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。
Exception.h/cc 提供异常类
- backtrace 栈回溯,保存各个栈帧的地址。
- backtrace——symbols,根据地址,转成相应的函数符号。
- c++会在编译时会对函数做名字改编(name mangling)。
- abi::__cxa_demangle 对c++的程序做名字还原。
Thread.h/cc 线程类
- Linux中进程与线程
- Linux中每个进程有一个pid,类型是pid_t,由getpid()取得。
- Linux下每个POSIX线程也有一个id,类型是pthread_t,由pthread_self()取得
- POSIX线程id由线程库维护,其id空间是各个进程独立的。(即不同的进程中的线程可能有相同的id)。
- Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。
- 有时候我们可能需要知道线程的真实pid。比如进程P1要向进程P2中的某个线程发送信号时,既不能使用P2的pid,更不能使用线程的pthread_id,而只能使用该线程的真实pid,称为tid。
- 有一个函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过 Linux的系统调用syscall来获取。
- return syscall(SYS_gettid)
- 普通成员函数,隐含的第一个参数是 Thread*(this),调用是时候遵循 thiscall约定,某一个寄存器会保护this指针,不能直接作为pthread_create的入口函数。需要定义一个static的静态成员函数,传递this指针来调用。
- static AtomicInt32 numCreated_; //用来记录创建的线程数,原子操作类型,在Thread类的构造中加1,析构上减1.
- __thread 关键字可以定义每个线程各个的变量。只能用来修饰基本类型(内置类型(与C兼容的原始类型)或无构造函数的类(结构体)),POD类型。
- 将真实线程tid缓存起来,可以减少系统调用次数。
- namespace detail 内部使用的命名空间,对外不可见
- boost::is_same 用来判定两种类型是否是同一种类型
- pthread_atfork (void (prepare)(void), void(parent)(void), void(*child)(void));
- 调用fork时,内部创建子进程前在父进程中调用prepare
- 内部创建子进程成功后,父进程会调用parent
- 子进程会调用child
- fork 可能是在主线程中调用,也可能是在子线程中调用
- fork得到一个新进程,新进程只有一个执行序列,只有一个线程(调用fork的线程被继承下来)
- 实际上,对于编写多线程程序来说,我们最好不要再调用fork
- 不要编写多进程多线程程序,要么用多进程要么用多线程
- Linux中进程与线程
Mutex.h 提供锁
- MutexLocal类
- 继承自boot::noncopyable,不可拷贝的,对象语义
- MutexLockGuard类(更常用)
- 使用RAII技法(通过生存期来管理资源,在构造函数中申请资源,在析构函数中释放资源)封装的MutexLocal类
- MutexLocal类
Condition.h/cc 条件变量类(Condition类)
12345678910
participant Thread1participant mutexparticipant Thread2Thread1->mutex:锁住 mutexNote over Thread1: wait(等待条件成立)Thread2->mutex:锁住 mutexNote over Thread2: 更改条件(signal or broadcast)Note over Thread1: wait(条件成立)Thread2->mutex:解锁Thread1->mutex:解锁
需要理解其中的wait函数,其实做了三件事
- 释放当前解锁mutex
- 判断条件是否成立,如不成立,则阻塞当前线程
- 条件成立,加锁mutex
- CountDownLatch.h/cc 倒计时门闩类
- 可以用于所有子线程等待主线程发起“起跑”
- 可以用于主线程等待子线程初始化完毕才开始工作
- mutbale关键字,使const函数可以修改mutable数据成员的状态。
BlockingQueue.h/BoundedBlockQueue.h 无界/有界缓冲区模板类(BlockingQueue/BoundedBlockQueue 模板类) (实际为生产者->消费者模型)
123456789101112131415161718192021222324252627282930313233343536
title:用有界队列来讲解生产者与消费者模型participant 生产者1participant 生产者2participant 队列participant 消费者1participant 消费者2生产者1->队列: 队列是否已满?Note right of 生产者1: 使用的是nwait(条件变量)n利用第8条nCondition类。队列->生产者1: 列队未满生产者1->队列: 加锁生产者1->队列: 生产产品到队列中生产者1->队列: 解锁生产者1->消费者1: 队列非空生产者1->消费者2: 队列非空Note over 队列: 生产者工作流程已说明完毕n下面说明消费者工作流程消费者1->队列:队列是否非空队列->生产者1: 列队非空消费者1->队列: 加锁消费者1->队列: 消费队列中的产品消费者1->队列: 解锁消费者1->生产者1: 队列未满消费者1->生产者2: 队列未满#基本流程已说明完毕,下面写一些混乱的状态Note over 队列: 基本流程已说明完毕n下面随便写一些状态生产者2->队列: 队列是否已满?队列->生产者2: 队列未满生产者2->队列: 加锁生产者2->队列: 生产产品到队列中生产者2->队列: 解锁生产者1->队列: 队列是否已满?队列->生产者1: 队列已满生产者2->消费者1: 队列非空生产者2->消费者2: 队列非空生产者2->队列: 队列是否已满?队列->生产者2: 队列已满
- BoundedBlockingQueue中使用了 boost::circular_buffer(环形缓冲区)
- 可以使用数组与求模运算自己实现环形缓冲区(boost中并不是这样实现的)
ThreadPoll.h/cc 线程池类(ThreadPool类)(本质上也是生产者->消费者模型)
1234567
graph TB subgraph ThreadPoll Consumer["线程队列(消费者)"]-- "完成任务" -->任务队列 end subgraph ExternalThreadPool Producer["外部线程(生产者)"]-- "提出任务" -->任务队列 end
- muduo库中实现的是固定的线程池(即线程池中线程个数固定的),不需要考虑线程池中线程个数的动态伸缩。
- typedef boost::function
Task;
Singleton.h 线程安全的单例模板类(Singleton模板类)
继承自boost::noncopyable,不可以被拷贝,对象语义。
只有instance()一个公有静态(public static)成员函数
通过pthread_once()来保证线程安全 及 单例。
typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1]; //通过此语句要求T必须是完全类型。(只有前置声明的类是非完全类型)。此语句通过将数组下标设置为-1,来引发编译错误。
上一点在最新的编译器中编译时会报警告 [-Werror=unused-local-typedefs],需要改为以下形式
123456
#define COMPILE_ASSE 大专栏 muduo base库学习笔记RT_UNUSED_ATTRIBUTE __attribute__((unused))#else #define COMPILE_ASSERT_UNUSED_ATTRIBUTE #endif typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1] COMPILE_ASSERT_UNUSED_ATTRIBUTE;
::atexit(destroy) 注册销毁函数,在程序结束时会自动去执行销毁函数。
ThreadLocal.h 线程特定数据模板类(ThreadLocal模板类),主要目的是提供__thread 的扩展
在单线程程序中,我们经常使用“全局变量”以实现多个函数间共享数据
在多线程环境下,由于数据空间是共享的,因此全局变量也为所有线程所共有
但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却可以跨多个函数访问
POSIX线程库通过维护一定的数据结构来解决这个问题,这些数据称为线程特定数据(Thread-specific Data,TSD),也可程序线程本地存储(Thread-local storage)。
对于POD类型的线程本地存储,可以使用__thread关键字
POSIX提供了4个函数
- pthread_key_create 创建一个key,一旦一个线程创建了一个key,那么每一个线程都拥有了这个key。但可以为不同的线程指定不同的数据,通过下面的pthread_setspecific来实现。
- pthread_key_delete 删除这个key,并非删除实际数据,实际数据的删除,需要在create key时指定一个回调函数,由回调函数来销毁实际数据(在堆栈中)。
- pthread_getspecific 获取特定数据。
- pthread_setspecific 指定特定数据。
| | 线程0 | 线程… | 线程n |
| :——-: | :—–: | :—–: | :—–: |
| | 线程其他信息 | 线程其他信息 | 线程其他信息 |
| pkey[0] | 指针 | 指针 | 指针 |
| pkey[1] | 指针 | 指针 | 指针 |
| pkey[…] | … | … | … |
| pkey[127] | 指针 | 指针 | 指针 |
| 系统数据结构 | ———— | ———— | ———— |
| 线程分析的内存区域 | ———— | ———— | ———— |
| 堆栈地址 | 上面指针所指向 | 上面指针所指向 | 上面指针所指向 |
| 堆栈地址 | 的实际数据 | 的实际数据 | 的实际数据 |测试程序SingletonThreadLocal_test.cc中的代码有绕,需要慢慢推敲,弄清楚什么是单例的,什么是线程特定数据,就明白STL是什么了。
ThreadLocalSingleton.h 线程本地单例模板类(ThreadLocalSingleton模板类)
- 不需要线程安全,每一个线程都有一个T类型的单例对象,
- 继承自boost::noncopyable,不可以被拷贝,对象语义。
- 综合使用了__thread 与 TSD两种线程本地存储技术
- 存储T类型的指针(T*),因为指针是POD类型,因此使用__thread关键字。
- 利用RAII技法,嵌套一个Deleter类,来实现TSD。
Logging.h/cc LogStream.h/cc
日志的作用:
- 开发过程中:
- 调试错误 (包括编译错误 、运行错误、逻辑错误)
- 更好的理解程序
- 运行过程中:
- 诊断系统故障并处理
- 记录系统运行状态
- 开发过程中:
日志级别:
- TRACE 指出比DEBUG粒度更细的一些信息事件(开发过程中使用)
- DEBUG 指出对调试程序非常有帮助的细粒度信息事件(开发过程中使用)
- INFO 指出在粗粒度级别上突出强调程序的运行过程的信息事件(muduo库中默认此级别)
- WARN 系统能正常运行,但可能会出现潜在错误的情形
- ERROR 指出虽然发生错误 ,但仍然不影响系统的继续运行
- FATAL 指出每一个严重的错误事件,并导致程序退出
Log类使用时序图
Logger ==> lmpl ==> LogStream ==> operator<<() ==> FixedBuffer ==> g_output ==> g_flush
Logger 日志类外部封装(负责日志的级别) ==> lmpl与LogStream 日志类内部的实现(负责日志的格式化)==> operator<<() 负责日志类的输出 ==> FixedBuffer 日志输出的缓冲区 ==> g_output 指定日志输出到不同的设备 ==> g_flush 完成输出。
1234567
title:Logger类简化模型participant userparticipant Loggerparticipant LogStreamuser->Logger: 1:stream()Logger->LogStream: 2:operator <<()
注意在::fopen(“/tmp/muduo.log”,”ae”)中参数e,
e (since glibc 2.7)
Open the file with the O_CLOEXECflag. See open(2) for more information. This flag is ignored for fdopen().
LogStream类中
FixedBuffer类 缓冲区类,只接收非类型(int SIZE)参数的模板类,
- 继承自boost::noncopyable,不可以拷贝。
const常量 根据google的规范,使用k开头,如下:
12
const int kSmallBuffer = 4000;const int kLargeBuffer = 4000*1000;
下图是FixedBuffer类在内存中的示意图
| | 大小为SIZE的一块内存 |
| ———————- | ———— |
| data(起始地址)–> | 0 |
| | 1 |
| cur(当前地址,标识已使用的内存)–> | 2 |
| | … |
| | SIZE-2 |
| data_+SIZE-1–> | SIZE-1 |
Fmt类
断言T是算术类型
1
BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value == true);
bzero函数
123
::bzero(data_, sizeof data_);//等价于memset(data_, 0, sizeof data_);
bzero函数,包含于
中,是线程安全的,是POSIX标准函数,属于BSD系函数,没有在ANSI中定义,Windows平台与Mac平台都将不包含将函数,Linux下GCC编译器支持。(信息来源) 不太理解这里为什么使用了bzero函数,只有这篇文章bzero & memset置零的性能比较中讲了下在数组较小时,bzero效率比memset要高。另一个优点是使代码易读吧。另外man手册中说这个函数已经过时了。
The **bzero()** function is deprecated (marked as LEGACY in POSIX.1-2001); use **memset**(3) in new programs. POSIX.1-2008 removes the specification of **bzero()**. The **bzero()** function first appeared in 4.3BSD.
BOOST_AUTO_TEST_CASE
- boost自动测试框架(sudo apt-get install libboost-test-dev)
- std::numeric_limits
::max() std::numeric_limits ::min()
StringPiece类 —-google 高效字符串的实现
用来实现高效的字符串传递 这里可以是const char* 也可以是std::string也可以,并且这里不涉及内存拷贝
在STL中为了提供通用的操作而又不损失效率,我们用到了一种特殊的技巧,叫traits编程技巧。具体的来说,traits就是通过定义一些结构体或类,并利用模板特化和偏特化的能力,给类型赋予一些特性,这些特性根据类型的 不同而异。在程序设计中可以使用这些traits来判断一个类型的一些特性,引发C++的函数重载机制,实现同一种操作因类型不同而异的效果。延申阅读:C++ Template(繁体中文版)
1234567891011
// 这里对__type_traits进行特化,给StringPiece一些特性#ifdef HAVE_TYPE_TRAITS// This makes vector<StringPiece> really fast for some STL implementationstemplate<> struct __<muduo::StringPiece> { typedef __true_type has_trivial_default_constructor; typedef __true_type has_trivial_copy_constructor; typedef __true_type has_trivial_assignment_operator; typedef __true_type has_trivial_destructor; typedef __true_type is_POD_type;};#endif
LogFile类
- 日志滚动条件
- 文件大小(例如:每满1G就换一个文件)
- 时间 (例如,每一小时写一个文件)
- gmtime与gmtime_r
- gmtime不是线程安全的,返回的是一个tm指针,但是另一个线程可能也在调用gmtime,因此指针所指向的内存地址可能发生了变化。
- gmtime_r是线程安全的,虽然也返回了一个tm指针,但同时将结果保存在result中。
- 日志滚动条件
cmake的使用
- muduo库中使用到的编译选项
- -Wall //大部分警告
- -Wextra // 一些额外的警告
- -Werror //当出现警告时转为错误,停止编译
- -Wconversion //一些要能改变值的陷式转换,给出警告
- -Wno-unused-parameter //函数中出现未使用的参数,不给出警告
- -Wold-style-case //C风格的转换,给出警告
- -Woverloaded-virtual //如果函数的声明隐藏住了基类的虚函数,就给警告
- -Wpointer-arith //对函数指针或者void*类型的指针进行算术操作时给出警告
- -Wshadow //当一个局部变量遮盖住了另一个局部变量,或者全局变量时,给出警告
- -Wwrite-strings //替换字符串常量的类型时const char[length],因此,把这样的地址复制给non-const char* 指针将产生警告。这些警告能够帮助你在编译期间发现企图写入字符串常量的代码
- -march=native //指定cpu体系结构为本地平台
- 无锁队列的实现
- O_CLOEXEC 的详细解释与具体作用
- boost自动测试框架
来源:https://www.cnblogs.com/liuzhongrong/p/12289483.html