最近在使用自編工具處理 unix 系統(tǒng)任務(wù)時(shí),遇到了兩個(gè)意料之外的情況,并非程序錯(cuò)誤,而是行為超出了預(yù)期。
我編寫了一個(gè) C 程序,用于讀取磁盤上的圖像,進(jìn)行處理,并將結(jié)果輸出到標(biāo)準(zhǔn)輸出 (STDOUT)。簡(jiǎn)化后的代碼如下:
for (imagefilename in images) { results = process(imagefilename); printf(results); }
圖像處理相互獨(dú)立,因此我嘗試使用 fork() 將處理任務(wù)分配到多個(gè) CPU 內(nèi)核以提高速度:
for (child in children) { pipe = create_pipe(); worker(pipe); } // 父進(jìn)程 for (imagefilename in images) { write(pipe[i_image % N_children], imagefilename); } worker() { while (1) { imagefilename = read(pipe); results = process(imagefilename); printf(results); } }
我創(chuàng)建管道進(jìn)行進(jìn)程間通信 (IPC),將文件名發(fā)送給子進(jìn)程 worker。每個(gè) worker 直接寫入共享的 STDOUT,導(dǎo)致輸出混亂。 flockfile() 函數(shù)無法解決問題,因?yàn)樗軐憰r(shí)復(fù)制機(jī)制的影響,每個(gè)子進(jìn)程都擁有鎖的副本。
我最終選擇使用線程而非 fork() 來解決此問題,避免了復(fù)雜的管道操作。 代碼如下:
for (children) { pthread_create(worker, child_index); } for (children) { pthread_join(child); } worker(child_index) { for (i_image = child_index; i_image < ... ) { // ... } }
這種方法更簡(jiǎn)潔有效??磥恚承┣闆r下線程比進(jìn)程更適用。
對(duì)于某些 vnlog 工具,我需要實(shí)現(xiàn)以下操作序列:
- 進(jìn)程打開一個(gè)未設(shè)置 O_CLOEXEC 標(biāo)志的文件。
- 進(jìn)程讀取文件的一部分(例如,vnlog 中的圖例結(jié)尾)。
- 進(jìn)程調(diào)用 exec() 執(zhí)行另一個(gè)程序處理已打開文件的剩余部分。
第二個(gè)程序可能需要文件名而非文件描述符作為命令行參數(shù),因?yàn)樗赡茏孕姓{(diào)用 open()。傳遞文件名會(huì)導(dǎo)致重新打開文件并從頭開始讀取,這無法滿足需求。
我嘗試使用 /dev/fd/N 傳遞文件描述符,但它在 Linux 系統(tǒng)上表現(xiàn)得像符號(hào)鏈接,與傳遞文件名效果相同。
解決方法是使用管道而非文件。/dev/fd/N 在管道上能正確傳遞文件描述符。 這可以通過將 open(“filename”) 替換為 popen(“cat filename”) 來實(shí)現(xiàn),但這并非理想解決方案。 這在 BSD 系統(tǒng)上的表現(xiàn)可能有所不同。