新聞中心
最近我在用我編寫(xiě)的各種工具做更多 UNIX 下的事情,我遇到了兩個(gè)有趣的問(wèn)題。這些都不是 “bug”,而是我沒(méi)想到的行為。

線程安全的 printf
我有一個(gè) C 程序從磁盤(pán)讀取一些圖像,進(jìn)行一些處理,并將有關(guān)這些圖像的輸出寫(xiě)入 STDOUT。偽代碼:
for(imagefilename in images)
{
results = process(imagefilename);
printf(results);
}
對(duì)于每個(gè)圖像都是獨(dú)立處理的,因此我自然希望將處理任務(wù)分配在各個(gè) CPU 之間以加快速度。我通常使用 fork(),所以我寫(xiě)了這個(gè):
for(child in children)
{
pipe = create_pipe();
worker(pipe);
}
// main parent process
for(imagefilename in images)
{
write(pipe[i_image % N_children], imagefilename)
}
worker()
{
while(1)
{
imagefilename = read(pipe);
results = process(imagefilename);
printf(results);
}
}
這是正常的做法:我為 IPC 創(chuàng)建管道,并通過(guò)這些管道給子進(jìn)程 worker 發(fā)送圖像名。每個(gè) worker 能夠通過(guò)另一組管道將其結(jié)果寫(xiě)回主進(jìn)程,但這很痛苦,所以每個(gè) worker 都直接寫(xiě)入共享 STDOUT。這工作正常,但正如人們所預(yù)料的那樣,對(duì) STDOUT 的寫(xiě)入發(fā)生沖突,因此各種圖像的結(jié)果最終會(huì)混雜在一起。那很糟糕。我不想自己設(shè)置個(gè)鎖,但幸運(yùn)的是 GNU libc 為它提供了函數(shù):flockfile()。我把它們放進(jìn)去了……但是沒(méi)有用!為什么?因?yàn)?flockfile() 最終因?yàn)?fork() 的寫(xiě)時(shí)復(fù)制行為而被限制在單個(gè)子進(jìn)程中。即 fork()提供的額外安全性(與線程相比),這實(shí)際上最終破壞了鎖。
我沒(méi)有嘗試使用其他鎖機(jī)制(例如 pthread 互斥鎖),但我可以想象它們會(huì)遇到類似的問(wèn)題。我想保持簡(jiǎn)單,所以將輸出發(fā)送回父輸出是不可能的:這給程序員和運(yùn)行程序的計(jì)算機(jī)制造了更多的工作。
解決方案:使用線程而不是 fork()。這有制造冗余管道的好的副作用。最終的偽代碼:
for(children)
{
pthread_create(worker, child_index);
}
for(children)
{
pthread_join(child);
}
worker(child_index)
{
for(i_image = child_index; i_image printf(results);
funlockfile(stdout);
}
}
這更簡(jiǎn)單,如預(yù)期的那樣工作。我猜有時(shí)線程更好。
將部分讀取的文件傳遞給子進(jìn)程
對(duì)于各種 vnlog 工具,我需要實(shí)現(xiàn)這個(gè)操作序列:
-
進(jìn)程打開(kāi)一個(gè)關(guān)閉 O_CLOEXEC 標(biāo)志的文件
-
進(jìn)程讀取此文件的一部分(在 vnlog 的情況下直到圖例的末尾)
-
進(jìn)程調(diào)用 exec() 以調(diào)用另一個(gè)程序來(lái)處理已經(jīng)打開(kāi)的文件的其余部分
第二個(gè)程序可能需要命令行中的文件名而不是已打開(kāi)的文件描述符,因?yàn)榈诙€(gè)程序可能自己調(diào)用 open()。如果我傳遞文件名,這個(gè)新程序?qū)⒅匦麓蜷_(kāi)文件,然后從頭開(kāi)始讀取文件,而不是從原始程序停止的位置開(kāi)始讀取。在我的程序上不可以這樣做,因此將文件名傳遞給第二個(gè)程序是行不通的。
所以我真的需要以某種方式傳遞已經(jīng)打開(kāi)的文件描述符。我在使用 Linux(其他操作系統(tǒng)可能在這里表現(xiàn)不同),所以我理論上可以通過(guò)傳遞 /dev/fd/N 而不是文件名來(lái)實(shí)現(xiàn)。但事實(shí)證明這也不起作用。在 Linux上(再說(shuō)一次,也許是特定于 Linux)對(duì)于普通文件 /dev/fd/N 是原始文件的符號(hào)鏈接。所以這最終做的是與傳遞文件名完全相同的事情。
但有一個(gè)臨時(shí)方案!如果我們正在讀取管道而不是文件,那么沒(méi)有什么可以符號(hào)鏈接,并且 /dev/fd/N 最終將原始管道傳遞給第二個(gè)進(jìn)程,然后程序正常工作。我可以通過(guò)將上面的 open(“filename”) 更改為 popen(“cat filename”) 之類的東西來(lái)偽裝。呸!這真的是我們所能做到最好的嗎?這在 BSD 上看上去會(huì)怎么樣?
本文名稱:UNIX下的趣聞趣事
網(wǎng)站網(wǎng)址:http://www.dlmjj.cn/article/cogpeei.html


咨詢
建站咨詢
