背景介绍

这篇文章主要针对C++11标准发布之后的现代C++的并发编程进行阐述。C++11首次在语言层面承认了多线程的存在,这使得“仅仅使用C++标准库就能编写跨平台的多线程程序”的愿望成为现实。

设计多线程的程序目的主要有两个:充分利用多核CPU的性能(利用多核心的计算能力以及让计算和IO重叠来降低RT并提升吞吐量)和简化程序逻辑(即把单线程状态机的逻辑拆分成多个线程彼此同步,这么做虽然不见得能提升代码性能,但是可以简化代码逻辑)。然而有些场合是不合适使用多线程的,最常见的两种场景是:限制CPU使用率线程代码中调用了fork(2)。前者不用解释,后者道理也很简单,因为fork(2)这个系统调用只会复制当前调用了该系统调用的线程,而其它线程并不会原样复制。如果此线程执行路径上的某个互斥锁已被没有原样复制的线程持有,那么该线程将永远死锁。只复制当前线程的行为也很合理。因为其它线程可能等在IO上,可能持有某些互斥锁,这些都使得forkall这样的行为难以实现。除非立即在调用fork(2)后调用exec(2),否则在线程代码里调用fork(2)可不是什么好主意。

其他的非必要场景也可以举一个例子,比如少量的CPU负载就能把IO跑满(静态web或者文件下载服务器)。这样的场景没有必要使用多线程,因为增加线程数也没有办法提高吞吐量。

Read More

最近在工作中遇到了需要精确测量一段C代码执行时间的需求,大家给出的方案有以下三种:

  • gettimeofday(2)
  • rdtsc/rdtscp
  • clock_gettime(2)

下面我们就逐一介绍下这三种方案的用法和限制,主要的关注点是准确性、精度和调用成本,讨论环境是运行在Intel x86上的Linux x86_64系统,内核的版本号高于2.6.32。

gettimeofday(2)

首先是gettimeofday(2),函数原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};

struct timezone {
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of DST correction */
};

Read More

前几天实验室的群里扔出了这样一个问题:TCP连接建立的三次握手过程可以携带数据吗?突然发现自己还真不清楚这个问题,平日里用tcpdump或者Wireshark抓包时,从来没留意过第三次握手的ACK包有没有数据。于是赶紧用nc配合tcpdump抓了几次包想检验一下。但是经过了多次实验,确实都发现第三次握手的包没有其它数据(后文解释)。后来的探究中发现这个过程有问题,遂整理探究过程和结论汇成本文,以供后来者参考。

先来张三次握手的图(下面这张图来自网络,若侵犯了作者权利,请联系我删除):

RFC793文档里带有SYN标志的过程包是不可以携带数据的,也就是说三次握手的前两次是不可以携带数据的(逻辑上看,连接还没建立,携带数据好像也有点说不过去)。重点就是第三次握手可不可以携带数据。

先说结论:TCP协议建立连接的三次握手过程中的第三次握手允许携带数据

Read More

Tair是一个高性能、分布式、可扩展、高可靠的NoSQL存储系统。本文基于Tair v3.1.2.43版本,探究其mdb存储引擎的实现。

Tair目前有mdb、ldb和rdb等存储引擎。其中mdb是Tair最早的一款内存型产品,也是在公司内部应用最广泛的集中式缓存。特别适用容量小(一般在M级别,50G之内),读写QPS高(万级别)的应用场景。由于是内存型产品,因此无法保证数据的安全性,对数据安全有要求的应用建议在后端加持久化数据源(例如MySQL)。本文接下来详细讨论Tair mdb存储引擎的实现。

Tair的存储引擎接口是src\storage\storage_manager.hpp里的虚基类storage_manager。所有的Tair存储引擎均继承实现了storage_manager这个虚基类。src\dataserver\tair_manager.cpp文件中的tair_manager::initialize函数根据配置文件中storage_engine的设置初始化相应的存储引擎。

Read More