putchar的线程安全

本文探讨了在多线程环境中,putchar函数可能导致的缓冲区破坏问题。由于线程间的并发执行,一个线程可能在更新tail指针前被挂起,导致另一个线程写入数据时覆盖了前一个线程的数据。为解决这个问题, ANSI C 提供了getchar_unlock和putchar_unlock等不加锁的快速函数,但使用它们需要配合flockfile和funlockfile来手动管理锁,以确保线程安全。

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

       前面调用的stdio函数一般都是printf和fgets。这些函数都是一次读入或输出一串数据。他们的操作是满足原子性的。ANSI C中同时还有putchar和getchar这样的函数。在新版本的putchar和getchar都是有加锁操作的,用来保护缓冲区不被破坏。我开始觉得这有点不好理解,以为单个字符原子性是可以保证的,其实不然。假设putchar用如下的伪代码实现:
int putchar(char c, FILE *stream)
{
		//获取缓冲区的队尾指针
		char *tail = gettail(stream);
		//移动指针
		tail++;
		//将字符写入缓冲区
		*(tail) = c;		
		return 0;
}

        问题一下就暴露出来了,两个线程都在调用putchar(),缓冲区的信息就会成为互斥资源。比如如下的情形,线程一在tail++执行之前被挂起了,线程二进入运行,它完整的执行了一次putchar。随后线程一又重新开始运行,它并不知道它获得的tail指针已经不是指向缓冲区末端了,它后续的写入操作将会覆盖掉线程二的写入数据。这大概就是传说中的缓冲区破坏了吧!

        现在的getchar和putchar都是可以保护缓冲区不被破坏的,但是保护就意味着加锁和效率降低。为了兼顾效率,ANSI C里面还提供了getchar_unlock和putchar_unlock这些不实现缓冲区保护的快速函数。不过使用这些函数的时候就要注意加flockfile和funlockfile自行对输入输出加锁。下面的代码展示了各函数的区别:

/*
 * putchar.c
 *
 * Demonstrate use of stdio file locking to generate an "atomic"
 * sequence of character writes (using putchar). If run with an
 * argument of "1", or no argument, the program uses a sequence
 * of putchar_unlocked calls within flockfile/funlockfile to
 * ensure that one threads writes cannot be interleaved with
 * another's.
 *
 * With an argument of "0", the program uses putchar, without
 * file locks, to show that the writes may be interleaved.
 *
 * The putchar[_unlocked] loop is punctuated with sleep(1) calls
 * to ensure that the desired behavior is demonstrated. Without
 * some delay, even on a multiprocessor the program may often
 * fail to display the interleaved output in this simplified
 * case.
 *
 * With file locking, you can expect to see the following output:
 *
 *      thread 1
 *      thread 2
 *      thread 3
 *
 * While without file locking, you can expect to see something
 * much less predictable, but probably resembling this:
 *
 *      ttthhhiiisss   iiisss   ttthhhrrreeeaaaddd   123
 *
 */
#include <pthread.h>
#include "errors.h"

/*
 * This function writes a string (the function's arg) to stdout,
 * by locking the file stream and using putchar_unlocked to
 * write each character individually.
 */
void *lock_routine (void *arg)
{
    char *pointer;

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

/*
 * This function writes a string (the function's arg) to stdout,
 * by using putchar to write each character individually.
 * Although the internal locking of putchar prevents file stream
 * corruption, the writes of various threads may be interleaved.
 */
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 *);
    int status;

    if (argc > 1)
        flock_flag = atoi (argv[1]);
    if (flock_flag)
        thread_func = lock_routine;
    else
        thread_func = unlock_routine;
    status = pthread_create (
        &thread1, NULL, thread_func, "this is thread 1\n");
    if (status != 0)
        err_abort (status, "Create thread");
    status = pthread_create (
        &thread2, NULL, thread_func, "this is thread 2\n");
    if (status != 0)
        err_abort (status, "Create thread");
    status = pthread_create (
        &thread3, NULL, thread_func, "this is thread 3\n");
    if (status != 0)
        err_abort (status, "Create thread");
    pthread_exit (NULL);
}
        当你选择unlock_routine时,它调用的putchar可以保证缓冲区不被破坏,但很可能会出现字符顺序错误的情况。选用lock_routine时,自己加的文件锁不仅保证了缓冲区不被破坏,还可以保证字符出现的顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值