分析上述汇编代码。首先获取 guard 变量,判断低字节是否为 0,若非零,表示已经初始化,可以直接使用。否则,将 guard 作为参数调用 __cxa_guard_acquire,如果锁成功,调用 init() 初始化静态变量 foo()::n,然后释放锁。如果锁失败,说明产生竞态条件,则会阻塞当前线程,不同于普通锁的地方在于,__cxa_guard_acquire 是有返回值的(当然 pthread_lock 也有返回值,但用途不同),如果发生了等待,__cxa_guard_acquire 返回 0,并不会进入 foo()::n 的初始化过程(其他线程已经初始化过了,初始化失败的情况就不细究了)。
  为了验证上述分析,可以将 init() 实现成一个耗时的操作,令多个线程“同时”调用 foo(),然后查看各个线程的运行状态。
  对于单线程程序,静态变量的保护是没有必要的,g++ 的 -fno-threadsafe-statics 选项可以禁掉该机制。

Tags: ,.

  多线程程序中,默认情况下各个线程的名字与进程名相同,但是可以通过操作系统提供的接口(prctl)修改这个名字。方法很简单,但某些时候,可以给程序的调试和运维带来很大的帮助。
  最近在调优tair的proxy server,其中使用到了tair client,每个tair client会创建若干个线程用于网络IO,另外proxy server本身还有其他IO线程。压测过程中发现,某个线程CPU彪到了100%,其他线程只有50%左右。根据该线程的调用栈可以推测出是tair client的IO线程,但其他IO线程不好分辨属于tair client还是proxy server。将IO线程按照功能命名后,发现4个tair client IO线程中,一个线程跑满了所在CPU核心,其他三个一直处于空闲状态。因此可以推断出tair client在连接分配上存在问题……找到了问题根源,解决起来就很容易了。

Tags: .

  最近看了些Memory Barrier的东西,针对一个经典的示例程序,做了重现性测试。
  对这个程序原型做一个简要介绍:有全局变量x/y/r1/r2,初始值均为0;线程1执行x = 1; r1 = y; 线程2执行y = 1; r2 = x。从“程序员角度”,在两个序列执行过后,r1 == r2 == 0不可能成立。但当发生执行时“访存重排序”时,却可能出现r1/r2同时为0的情况。使用CPU内存屏障适当“隔离”访存操作,杜绝重排序,就可以确保程序得到预想的结果。

Tags: .

  今天通过酷壳一篇推荐了解了一下 Cache 的“伪共享”(False Sharing). 写了小程序做了个简单的测试:

Tags: .

  经常需要查看一个文件有多少页面驻留在物理内存中,Linux中没有找到现成的命令,自己动手写了个小工具,基于mincore(2)系统调用。代码放在Github上面,可以git clone git://github.com/dutor/mincore.git获取。
  目前可用选项有限:查看特定文件驻留物理内存的页面数/字节数;将文件全部加载到物理内存;递归地查看某个目录下所有文件的映射情况。其他需要的功能,可能在以后添加。
  另外有一个叫vmtouch的工具,可以提供更丰富的功能。

Tags: .

  偶然发现glibc提供的lib.so是“可执行的”,这是快速查看glibc版本的一种方法,

1
2
3
4
5
6
7
~$ /lib/i386-linux-gnu/libc.so.6
GNU C Library (Ubuntu EGLIBC 2.13-20ubuntu5.1) stable release version 2.13, by Roland McGrath et al.
Copyright (C) 2011 Free Software Foundation, Inc.
...
Compiled by GNU CC version 4.6.1.
Compiled on a Linux 3.0.17 system on 2012-03-07.
...

  这是gcc提供的一种叫做PIE(Position Independent Executable)的特性,可以创建位置无关的可以执行对象。它可以像共享库一样被加载到任何地址上执行。

Tags: ,.

  事情是这样的。我们对tair(淘宝的分布式Key/Value系统)动了一次大手术,更换了网络框架,经过长时间的测试/调试,终于完全通过了回归测试。但要打包发布的时候,却发现服务器可以正常启动,但却完全无法接受请求。调试无果,对比打包前后程序的差异,仅在于是否使用-O2选项对程序进行编译优化。
  无头苍蝇一样,Google搜索“gcc optimization problems”,找到StackOverflow上面的这个帖子,“抱着试试看的心态”,在编译选项中加入-fno-strict-aliasing,bingo!
  -fno-strict-aliasing这个选项是做什么的?aliasing又是什么?C和C++的标准给出了说明:

Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to 
objects of different types will never refer to the same memory location (i.e. alias eachother.)
Tags: ,,.

  pthread的mutex通常用在多线程的同步当中,至于多进程的同步,一直以为只能使用记录锁和信号量,而这两种机制都需要内核的支持,属于“重量级”部件。也曾经在多进程同步中使用pthread mutex,但前提有两个:mutex能为多个进程所见,使mutex对象驻留在共享内存中;mutex本身不额外使用进程本地的内存,如堆内存。第一个前提容易满足,对于第二个,GCC的pthread实现也满足。但我一直没有注意到的是,pthread mutex标准本身对多进程提供了“可选”支持,只要实现支持、初始化mutex时pthread_mutexattr_t的pshared成员置为PTHREAD_PROCESS_SHARED即可。
  pshared默认为PTHREAD_PROCESS_PRIVATE,即仅支持单进程。如果mutex驻留于共享内存,但pshared为PTHREAD_PROCESS_PRIVATE,此时多进程操作该mutex的行为是未定义的。这两天在写一个多进程可访问的库时就出现了这种“未定义”的行为,花费了一个多工作日才找到原因。

Tags: ,.

这两个程序的输出均是:

pure virtual method called
terminate called without an active exception
Aborted (core dumped)

原因和父类/子类的构造、析构的顺序,以及vtbl的相应变化有关,有兴趣的同学可以研究下。
注:纯虚函数在纯虚类的vtbl中的对应表项为函数__cxa_pure_virtual(),该函数会调用terminate(),后者打印”pure virtual method called”信息后调用abort自杀。

Tags: .

  这么一段程序,

1
2
3
4
5
6
7
8
9
typedef __gnu_cxx::hash_map<object *, object *, object_equal_to, object_hasher> object_hash_map_t;
object_hash_map_t hash_map;
hash_map.insert(make_pair(new object, new object));
object_hash_map_t::iterator it = hash_map.begin();
while (it != hash_map.end()) {
    delete it->first;
    delete it->second;
    ++it;
}

  了然了,iterator的operator++需要根据key计算出迭代器当前所在的bucket,然后在此bucket及后续bucket中顺序遍历以寻找下一个元素。所以在开头的示例代码中,由于在迭代过程中释放了key的内存,当迭代器“递增”计算当前bucket number时当然就core掉了。
  hash_map在STL中已经被标记为deprecated,已经过时了,若要使用hash_map,建议使用具有相同接口和特性的unordered_map。
  另外,使用hash_map时,一定要注意模板参数equal_to和hash_func,前者用来判断key是否相等,后者用来计算key的hash值,稍有不慎就会出现莫名其妙的错误,例如著名的hash_map的遍历死循环问题。

Tags: ,.
Page 2 of 3312345678910...2030...Last »