POSIX针对pthread线程的调整

这篇博客探讨了在POSIX多线程环境中,如何正确处理fork、exec、exit操作以及stdio的线程安全问题。详细介绍了flockfile和funlockfile在stdio流锁定中的作用,以及线程安全函数如_getchar_unlocked和_putchar_unlocked的使用。同时,讨论了信号处理和信号灯在多线程程序中的应用与注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《POSIX多线程程序设计》——David R.Buten

Pthreads改变了很多POSIX进程函数的含义。

fork

应尽量避免在一个多线程的程序中使用fork。
当多线程进程调用fork创造子进程时,Pthreads指定只有那个调用fork的线程在子进程内存在。子进程拥有与父进程相同的互斥量、线程私有数据键等。fork调用不会影响互斥量的状态。如果它在父进程中被锁住,则它在子进程中被锁!

exec

对exec的调用,将很快终止进程内除调用exec的线程外的所有线程。它们不执行清除处理器或线程私有数据destructors。使用此函数,你应该解锁当前进程可能锁住的任何pshared互斥量。

exit

exit函数提供了一个简单的方法停止整个进程。当程序某种程度上被破坏时,试图干净地停止应用线程可能是危险的。在这种情况下,可以调用exit来很快地结束所有的处理。

stdio

Pthreads要求ANSI C标准I/O(stdio)函数时线程安全的。因为stdio包需要为输出缓冲区和文件状态指定静态存储区,stdio实现将使用互斥量和信号灯等同步机制。

flockfile和funlockfile

有时保证一系列stdio操作以不被中断的顺序执行是重要的。例如,一个提示符后面跟着一个从终端的读操作,或者一起在输出文件出现的两个写操作。因此,Pthreads增加了锁住一个文件的机制并且规定文件锁如何与stdio内部锁交互。
(例子:向stdout写提示符号并从stdin读响应)

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void* prompt_routine (void *arg) {
    char *prompt = (char *)arg;
    char *string;
    int len;

    string = (char *)malloc(128);
    flockfile(stdin);
    flockfile(stdout); // Pthreads推荐总是在输出流之前锁住输入流
    printf("%s", prompt);
    if (fgets(string, 128, stdin) == NULL)
        string[0] = '\0';
    else {
        len = strlen(string);
        if (len > 0 && string[len-1] == '\n')
            string[len-1] = '\0';
    }
    funlockfile(stdout); // 考虑一致的锁层次,先解锁输出流后解锁输入流
    funlockfile(stdin);
    return (void *)string;
}

int main(int argc, char *argv[]) {
    pthread_t thread1, thread2, thread3;
    char *string;

    pthread_create(&thread1, NULL, prompt_routine, "Thread 1> ");
    pthread_create(&thread2, NULL, prompt_routine, "Thread 2> ");
    pthread_create(&thread3, NULL, prompt_routine, "Thread 3> ");

    pthread_join(thread1, (void**)&string);
    printf("Thread 1: \"%s\"\n", string);
    free(string);
    pthread_join(thread2, (void**)&string);
    printf("Thread 1: \"%s\"\n", string);
    free(string);
    pthread_join(thread3, (void**)&string);
    printf("Thread 1: \"%s\"\n", string);
    free(string);
    return 0;
}

结果:

Thread 1> 12
Thread 2> 34a
Thread 1: "12"
Thread 1: "34a"
Thread 3> abcd 23
Thread 1: "abcd 23"

getchar_unlocked和putchar_unlocked

ANSI C提供了向stdio缓冲区高效地读取和写入单个字符的函数(宏实现)。Pthreads增加了新函数来代替旧的高效宏,这些函数本质上与传统的宏实现一样,并且不执行任何锁操作,但可以和文件锁一起使用,用于满足Pthreads要求的锁住stdio流数据来防止代码对stdio缓冲区的偶然破坏。

#include <pthread.h>
#include <stdio.h>

void* lock_routine(void *arg) {
    char *pointer;

    flockfile(stdout);
    for (pointer = arg; *pointer != '\0'; pointer++) {
        putchar_unlocked(*pointer);
        sleep(1);
    }
    funlockfile(stdout);
    return NULL;
}

void* unlock_routine(void *arg) {
    char *pointer;

    for (pointer = arg; *pointer != '\0'; pointer++) {
        putchar(*pointer);
        sleep(1);
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    pthread_t thread1, thread2, thread3;
    int flock_flag = 1;
    void* (*thread_func)(void *);

    if (argc > 1)
        flock_flag = atoi (argv[1]);
    if (flock_flag)
        thread_func = lock_routine;
    else
        thread_func = unlock_routine;
    pthread_create(&thread1, NULL, thread_func, "this is thread 1\n");
    pthread_create(&thread2, NULL, thread_func, "this is thread 2\n");
    pthread_create(&thread3, NULL, thread_func, "this is thread 3\n");
    pthread_exit(NULL);
    return 0;
}

结果:
1

线程安全的函数

Pthreads定义了现存函数的线程安全的变体,它们在相应函数名结尾处添加后缀_r。

  • 用户和终端ID
    getlogin_r、ctermid、ttyname_r
  • 目录搜索
    readdir_r(两个线程并行搜索同一目录,必须同步传给readdir_r函数的共享struct dirent的使用)
  • 字符串
    token
  • 时间表示
    asctime_r、ctime_r、gmtime_r、localtime_r
  • 随机数
    read_r
  • 组和用户数据库
    getgrgid_r、getgrnam_r、getpwuid_r、getpwnam_r

信号

  • 避免与线程一起使用信号总是最好的,但将它们分开经常又是不可能或不实际的。
  • 当多线程工作时,向进程内任何线程传送一个SIGKILL信号将终止进程。传送一个SIGSTOP信号将导致所有的线程停止直到收到SIGCONT信号。
  • 每个线程有自己私有的信号掩码,可通过pthread_sigmask来修改。当一个线程被创建时,它继承了创造它的线程的信号掩码。(如果想在所有地方屏蔽一个信号,则首先在主线程中屏蔽它
  • 在一个进程内,一个线程可调用pthread_kill想一个特定线程(包括自己)发信号。如果线程没有将此信号屏蔽,并且没在sigwait中阻塞,此信号的行为将被执行。对于多线程而言,Pthreads指定raise(SIGABRT)pthread_kill(pthread_self(), SIGABRT)一样。
  • Pthreads增加了允许线程代码同步地处理异步信号的函数(sigwait类似函数)。在多线程代码中总是使用sigwait与异步信号一起工作。调用sigwait等待的信号必须在调用线程中被屏蔽,而且通常应该在所有的线程中被屏蔽。如果两个线程在sigwait上阻塞,只有一个线程将收到送给进程的信号。
  • sigwait作为一个Pthreads函数,通过返回一个错误数字来报告错误;而它的兄弟函数sigwaitinfo和sigtimedwait在Pthreads以前就被加到POSIX中,它们使用旧的errno机制。sigtimedwait如果在指定的间隔内没有收到选定信号,则返回EAGAIN错误。
  • 在POSIX.1b中,Pthreads增加了被称为SIGEV_THREAD的新通知机制。
    (范例:sigwait处理SIGINT信号(Ctrl+c))
#include <pthread.h>
#include <signal.h>
#include <stdio.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int interrupted = 0;
sigset_t signal_set; // 信号集

void* signal_waiter(void *arg) {
    int sig_number;
    int signal_count = 0;

    while (1) {
        sigwait(&signal_set, &sig_number); // 同步处理异步信号
        if (sig_number == SIGINT) {
            printf("Got SIGINT (%d of 5)\n", signal_count+1);
            if (++signal_count >= 5) {
                pthread_mutex_lock(&mutex);
                interrupted = 1;
                pthread_cond_signal(&cond);
                pthread_mutex_unlock(&mutex);
                break;
            }
        }
    }   
    return NULL;
}

int main(int argc, char *argv[]) {
    pthread_t signal_thread_id;

    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &signal_set, NULL); // 屏蔽一些信号(SIGINT)(必须在sigwait调用前)

    pthread_create(&signal_thread_id, NULL, signal_waiter, NULL); // 线程会从创造者那儿继承起始信号掩码

    pthread_mutex_lock(&mutex);
    while (!interrupted)
        pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);
    printf("Main terminating with SIGINT\n");
    return 0;
}

结果:

^CGot SIGINT (1 of 5)
^CGot SIGINT (2 of 5)
^CGot SIGINT (3 of 5)
^CGot SIGINT (4 of 5)
^CGot SIGINT (5 of 5)
Main terminating with SIGINT

(范例:SIGEV_THREAD信号像线程一样处理计时器消息)

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>

timer_t timer_id;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int counter = 0;

void timer_thread(union sigval arg) {
    pthread_mutex_lock(&mutex);
    if (++counter >= 5)
        pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    printf("Timer %d\n", counter);
}

int main(int argc, char *argv[]) {
    struct itimerspec ts;
    struct sigevent se;

    se.sigev_notify = SIGEV_THREAD; // Pthreads增加了此通知机制,使信号通知函数像线程起始函数一样运行
    se.sigev_value.sival_ptr = &timer_id;
    se.sigev_notify_function = timer_thread; // 线程起始函数
    se.sigev_notify_attributes = NULL; // 线程属性(默认PTHREAD_CREATE_DETACHED)

    ts.it_value.tv_sec = 5;
    ts.it_value.tv_nsec = 0;
    ts.it_interval.tv_sec = 5;
    ts.it_interval.tv_nsec = 0;

    printf("Creating timer\n");
    timer_create(CLOCK_REALTIME, &se, &timer_id); // 创建一个进程范围内的定时器
    printf("Setting timer %d for 5-second expiration...\n", (int)timer_id);
    timer_settime(timer_id, 0, &ts, 0); // 每次定时器到期时都会调用timer_thread

    pthread_mutex_lock(&mutex);
    while (counter < 5)
        pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);

    return 0;
}

结果:

Creating timer
Setting timer 163066008 for 5-second expiration...
Timer 1
Timer 2
Timer 3
Timer 4
Timer 5

信号灯

如果你的系统支持POSIX信号灯,在<unistd.h>中定义了_POSIX_SEMAPHORES选项。
“锁”信号灯——创建一个初始值为1的信号灯,允许一个线程不必阻塞就完成sem_wait操作。
“等待”信号灯——创建一个初始值为0的信号灯,调用sem_wait的线程阻塞直到其他线程调用sem_post。
(范例:“等待”信号灯的使用)

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>

sem_t semaphore;

void *sem_waiter (void *arg) {
    long num = (long)arg;

    printf ("Thread %ld waiting\n", num);
    sem_wait(&semaphore); // sem > 0 : sem减1后立即返回 : 阻塞
    printf("Thread %ld resuming\n", num);
    return NULL;
}

int main(int argc, char *argv[]) {
    int thread_count;
    pthread_t sem_waiters[5];

    sem_init(&semaphore, 0, 0);
    for (thread_count = 0; thread_count < 5; thread_count++) 
        pthread_create(&sem_waiters[thread_count], NULL, sem_waiter, (void *)thread_count);
    sleep(2);

    while (1) {
        int sem_value;
        sem_getvalue(&semaphore, &sem_value);
        if (sem_value >= 5) // 5个线程阻塞等待,唤醒5次
            break;
        printf("Posting from main: %d\n", sem_value);
        sem_post(&semaphore); // sem++
    }
    for (thread_count = 0; thread_count < 5; thread_count++)
        pthread_join (sem_waiters[thread_count], NULL);
    return 0;
}

结果:

Thread 2 waiting
Thread 0 waiting
Thread 1 waiting
Thread 3 waiting
Thread 4 waiting
Posting from main: 0
Posting from main: 1
Posting from main: 2
Posting from main: 1
Thread 3 resuming
Thread 2 resuming
Thread 0 resuming
Posting from main: 0
Posting from main: 1
Posting from main: 2
Posting from main: 3
Posting from main: 3
Posting from main: 4
Thread 4 resuming
Thread 1 resuming
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值