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