Threads:Async-Signal-Safe and Deadlock


网上找的,调整了下格式,东西是好东西,格式太丑了。留着以后看。


Signals are delivered asynchronously, much like interrupts, and so there are a great deal of restrictions placed on the code which runs. Many of these restrictions are much like ThreadSafety , in that you have to account for the fact that your signal handler could run at any moment while other code is running, causing weird problems.

Here is an example of code which is safe:

volatile int x = 0;
void handler(int signal) {
    x++;
}

int main(void) {
    signal(SIGHUP, handler);
    while(1) {
        sleep(1);
        printf("x = %d/n", x);
    }
}

Just like with threads, if the signal handler is writing to a primitive and the main function is only reading, everything is safe. Well, as long as no one else sends a SIGHUP: if that were to happen, then the above example has the potential of a race condition.

Now for a broken example:

volatile int x = 0;
void handler(int signal) {
    x++;
}


int main(void) {
    signal(SIGHUP, handler);
    while(1) {
        sleep(1);
        x++;
        printf("x = %d/n", x);
    }
}

This is broken for the same reason that it would be broken with two threads. The operation x++ is not necessarily atomic, but can be divided into several pieces:

  • read x from memory into a register
  • increment the register
  • write x back into memory

If the signal handler is invoked while the main function is in the middle of this update process, an increment will be lost. Worse, the read and write of x is not guaranteed to be atomic (for that we would need to use sig_atomic_t ). Let's try to fix this by adding a simple lock. We'll assume that we have a TestAndSet? function which atomically sets a variable to 1 and returns its old value. Then we write our new program like this:

volatile sig_atomic_t lock = 0;
volatile int x = 0;

void handler(int signal) {
    while(TestAndSet(&lock)) ;
    x++;
    lock = 0;
}


int main(void) {
    signal(SIGHUP, handler);
    while(1) {
        sleep(1);
        while(TestAndSet(&lock)) ;
        x++;
        lock = 0;
        printf("x = %d/n", x);
    }
}

If we were working with threads, then everything would work as expected. But with signals, this not only fails to solve the problem, but it actually makes it worse . Why?
Threads run more or less simultaneously. On a multi-processor system they might really run simultaneously, but even on a single-processor system, the OS makes sure that every thread gets a chance to run. So while a thread might get stuck in the while loop for a while, eventually the other thread will get a chance to run, and it will unlock the lock.
Signals, however, don't run simultaneously. While the signal handler is running, the main program is completely stopped. If the handler is invoked while the main program has locked the lock, the handler will spin forever waiting for the lock to be unlocked, while at the same time the main program is stuck waiting for the handler to end. Deadlock! If this situation ever happens, your program will completely freeze. So now we see that signal-safe code is even more restricted than thread-safe code.
How do we fix it? For our example program, we can fix it by adding an auxiliary variable, like so:

volatile sig_atomic_t lock = 0;
volatile int x = 0;
volatile int y = 0;

void handler(int signal) {
    if(!lock)
        x++;
    else
        y++;
}


int main(void) {
    int temp;
    signal(SIGHUP, handler);
    while(1) {
        sleep(1);
        lock = 1
        x++;
        lock = 0;
        temp = y;
        y = 0;
        lock = 1;
        x += temp;
        lock = 0;
        printf("x = %d/n", x);
    }
}

Here, we have a sort of asymmetric lock. Instead of waiting for the lock to be free, the signal handler uses it to decide which variable to increment. The main program then manipulates the lock to ensure that it can always reliably read or modify the variable it's interested in while it performs the sum of the two.
But you say, this counter is nonsense. I just want to print out a notice that my signal was received, nothing more. I'll just write this code:

void handler(int signal) {
    printf("got signal %d/n", signal);
}
This code is fine, right? None of this nonsense with locks or counters or anything. No! It's not safe because you're calling printf() , and who knows what it does inside. In fact printf() probably does some locking internally on the file stream, so if the signal is delivered while your program is in the middle of another call to printf() , kaboom, deadlock!
What do we do? In this case, we'll have to do everything manually using functions which we know to be safe. In this case, we take advantage of the fact that write() is safe:

 

void handler(int signal) {
    char text[] = "got signal 00/n";
    text[11] += (signal / 10) % 10;
    text[12] += signal % 10;
    write(STDOUT_FILENO, text, 14);
}

The key word is "async-signal safe". If you see a function documented as being "thread safe", you know that you can call it simultaneously from multiple threads. If you see a function documented as being "async-signal safe", then you know that you can call it from a signal handler without blowing up your program. A fairly complete list of signal safe functions can be found in man sigaction .
The trick is that a lot of code is not async-signal safe. Since it's so much harder to write, very little code is async-signal safe. For example, objc_msgSend() uses locks and so is not async-signal safe, meaning that you cannot use any Objective-C code in a signal handler. You can't use Objective-C, you can't call malloc() , you can't touch CoreFoundation or most of libc.
How do you get anything accomplished in a signal handler, then? The best bet is usually to do as little as possible in the handler itself, but somehow signal the rest of your program that something needs to be done. For example, let's say you want to reload your configuration file when sent a SIGHUP. If your program never blocks for long, we could write our program like this:

volatile sig_atomic_t gReloadConfigFile = 0;

void handler(int signal) {
    gReloadConfigFile = 1;
}
...
while(!done) {
    DoPeriodicProcessing();
    if(gReloadConfigFile) {
        gReloadConfigFile = 0;
        ReloadConfigFile();
    }
}

What if your program often blocks on input or a socket or something of that nature? All is not lost, however, because delivering a signal will automatically unblock your program if it's in the middle of a blocking read() , select() , or similar.
You could write your program like this:

volatile sig_atomic_t gReloadConfigFile = 0;

void handler(int signal) {
    gReloadConfigFile = 1;
}
...
while(!done) {
    if(gReloadConfigFile) {
        gReloadConfigFile = 0;
        ReloadConfigFile();
    }
    // X
    select(...);
    ProcessData(...);
}

Looks good, right? If your program is blocked in the select() , the signal will dislodge it and the app will reload its configuration file. If the program is busy processing data when it comes in, then the configuration file will be reloaded before you get back to the select() . But... you guessed it! This code isn't completely safe!
The key is the // X comment. If the signal is delivered in that spot, after the check but before entering the select() , the select() will still block, and the configuration file won't be reloaded until some input arrives, which could be much later.
What do we do about this? The best way is to use a signaling mechanism which can be integrated into the select() call, namely a pipe. You can create a pipe using the pipe() system call, then read from it in the select() and write to it in your signal handler. By using the pipe instead of a global variable, you ensure that the select() never blocks when a condition needs to be taken care of. Implementation of this scheme is left as an exercise to the reader.
If you do use this scheme, you have to be careful. (I can hear you saying, "What now???") Make sure to set your pipe to be non-blocking, otherwise your pipe could fill up in the signal handler before its emptied in your main program, and you'll deadlock. If the write fails because the pipe is full then that's fine anyway, because that means there's already a signal sitting in the pipe ready to be received the next time you go through your app's main loop.

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