putchar的線程安全

       前面調用的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時,自己加的文件鎖不僅保證了緩衝區不被破壞,還可以保證字符出現的順序。

發佈了49 篇原創文章 · 獲贊 11 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章