可重入与线程安全

线程安全(thread safety)是指在多线程环境下,不同的线程在同一时刻能够安全访问临界区的能力,它可以让代码没有副作用地实现想要的功能。
可重入(reentrancy)是指一个函数如果在执行过程中被中断,当中断完成后又可以安全地进入上次中断点重新执行的能力。它有两种语义:

  • 在多线程环境下,一个线程因时间片使用完了(或者其他原因),另一个线程开始运行,接着该线程又安全地重新开始运行。在这种语境下,可重入等同于线程安全。
  • 在单线程的信号处理环境下,一个函数在运行过程中,此时异步来了个信号,控制流便转向了信号处理函数,当信号处理函数完成后该函数又可以安全地重新运行。在这种语境下,可重入又被称为异步信号安全(async-signal safety)。

当提到可重入的时候,我们一般指的是后者。


可重入

为了使函数达到可重入,需要遵循一定的规则,如下

  1. 不要包含静态数据,不要使用全局数据。
int global_var{10};

int NotReentrant()
{
  global_var = 20;
  // 在这里来了个信号
  return global_var;
}

如上所示,如果给 global_var 赋值之后来了个信号,在信号处理函数中又对 global_var 赋了不同的值,那么从信号处理函数返回到 NotReentrant 中,global_var 的值就不再是我们期望的值,因此该函数是不可重入的。
这个例子比较直观,信号也可能在一些不太直观的地方中发送过来。例如,在一个 32 位的机器上操作 64 位的数据,这个操作可能就要被分为两个 32 位的操作,而在这两个操作之间,信号就有可能被发送过来;对于 global_var = f() + g();f()g() 发生的先后顺序是不确定的,而且信号也可能在两个函数之间被发送过来。

  1. 不要使用 newmalloc)或 deletefree)。

不同实现中的 new 是不同的,可以是线程安全的也可以是线程不安全的,但无论如何都是不可重入的。
先假设它是线程不安全的。new 通常为它在堆上分配的存储区维护一个链表,而当信号来的时候,线程可能正在修改此链表,而信号处理函数中也可能调用了 new,也要修改链表,这就造成了冲突。因此线程不安全的 new 是不可重入的。
再假设它是线程安全的。这时候就要在修改链表的地方加上锁,如果在加上锁之后但还没有修改完链表的时候来了个信号,在信号处理函数中也调用了 new,也要加上锁,如果该锁不是递归的,那么该线程将会永久地等待该锁的释放,无法将控制流返回到之前的函数中。因此线程安全的 new 也是不可重入的。
在本文的测试环境中(Ubuntu-16.04-64bit GCC-5.4.0),newmalloc)和 deletemalloc)都是线程安全的。

  1. 不要使用不可重入的函数。

特别需要注意的是标准 I/0 函数,标准 I/O 库中的很多实现都以不可重入方式使用了全局数据。若标准 I/O 指向的是终端,则它是行缓冲的,否则是全缓冲的。例如对于 printf,并不是调用它就会立即将全局缓冲数据冲洗(flush),而是当遇到了换行符(行缓冲)或者是缓冲区满了(全缓冲)才会将数据传送。由于使用了全局数据,因此 printf 是不可重入的,不能将它用在可重入的函数中。

在本文的测试环境下,有些函数是不可重入的,例如 strerrorreaddir,但是系统提供了可重入的版本 strerror_rreaddir_r(后缀 r 表示 reentrant),这些可重入版本不再使用静态数据,而是需要调用者提供由自己管理的存储空间。
信号处理函数也需要是可重入的,当控制流在信号处理函数 A 中时,也可能会有另外的信号发送过来,如果此时的信号屏蔽字没有将该信号屏蔽掉,那么就会转到相应的信号处理函数 B 中,如果信号处理函数 A 和 B 都修改了同一个全局变量,那么结果将会是意料之外的。
对于以上的规则,errno 是一个例外,每个线程都会有自己的 errno,Single UNIX Specification 中要求的可重入函数(详见 APUE 第三版 10.6)也可能会出错,从而修改了 errno,但是依然认为这些函数是可重入的,所以如果在信号处理函数中调用了这些函数,需要在该信号处理函数开始的位置保存 errno,在函数的末尾再把保存的值重新赋给 errno


可重入与线程安全的区别

我们经常将可重入与线程安全视为相同的,但是它们之间还是有细微的差别。在多线程环境下,可重入即为线程安全;但是更常使用的语境是单线程的信号处理,因为满足了上述可重入的三个规则的函数,大多同时也是线程安全的,所以通常并不对其进行区分,但是也会有特殊的情况。

是可重入却是线程不安全

int global_var{20};

void Swap(int* lhs, int* rhs)
{
  int save{global_var};

  global_var = *lhs;
  *lhs = *rhs;
  // 假如信号在此时传来
  *rhs = global_var;

  global_var = save;
}

这种做法就类似与上文对 errno 的处理,先将 global_var 保存起来,在末尾的地方再还回去。如果信号在 Swap 中途传来,也不用担心控制流重新回来的时候 global_var 会发生改变,因此是可重入的;但是由于没有对临界区锁起来,这个函数就是线程不安全的。

是线程安全却是不可重入

上文中的线程安全的 new 就是一个例子。


参考

[1] Reentrancy(computing)
[2] Thread safety
[3] why are malloc and printf said as non-reentrant

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容