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