歡迎光臨
每天分享高質量文章

UNIX 的怪東西 | Linux 中國

最近我在用我編寫的各種工具做更多 UNIX 下的事情,我遇到了兩個有趣的問題。這些都不是 “bug”,而是我沒想到的行為。
— Dima Kogan


致謝
編譯自 | 
http://notes.secretsauce.net/notes/2018/08/03_unix-curiosities.html
 
 作者 | Dima Kogan
 譯者 | geekpi ???共計翻譯:783 篇 貢獻時間:1757 天

最近我在用我編寫的各種工具做更多 UNIX 下的事情,我遇到了兩個有趣的問題。這些都不是 “bug”,而是我沒想到的行為。

執行緒安全的 printf

我有一個 C 程式從磁碟讀取一些影象,進行一些處理,並將有關這些影象的輸出寫入 STDOUT。偽程式碼:

  1. for(imagefilename in images)

  2. {

  3.    results = process(imagefilename);

  4.    printf(results);

  5. }

對於每個影象都是獨立處理的,因此我自然希望將處理任務分配在各個 CPU 之間以加快速度。我通常使用 fork(),所以我寫了這個:

  1. for(child in children)

  2. {

  3.    pipe = create_pipe();

  4.    worker(pipe);

  5. }

  6. // main parent process

  7. for(imagefilename in images)

  8. {

  9.    write(pipe[i_image % N_children], imagefilename)

  10. }

  11. worker()

  12. {

  13.    while(1)

  14.    {

  15.        imagefilename = read(pipe);

  16.        results = process(imagefilename);

  17.        printf(results);

  18.    }

  19. }

這是正常的做法:我為 IPC 建立管道,並透過這些管道給子行程 worker 傳送影象名。每個 worker 能夠透過另一組管道將其結果寫回主行程,但這很痛苦,所以每個 worker 都直接寫入共享 STDOUT。這工作正常,但正如人們所預料的那樣,對 STDOUT 的寫入發生衝突,因此各種影象的結果最終會混雜在一起。那很糟糕。我不想自己設定個鎖,但幸運的是 GNU libc 為它提供了函式:flockfile()[1]。我把它們放進去了……但是沒有用!為什麼?因為 flockfile() 最終因為 fork() 的寫時複製行為而被限制在單個子行程中。即 fork()提供的額外安全性(與執行緒相比),這實際上最終破壞了鎖。

我沒有嘗試使用其他鎖機制(例如 pthread 互斥鎖),但我可以想象它們會遇到類似的問題。我想保持簡單,所以將輸出發送回父輸出是不可能的:這給程式員和執行程式的計算機製造了更多的工作。

解決方案:使用執行緒而不是 fork()。這有製造冗餘管道的好的副作用。最終的偽程式碼:

  1. for(children)

  2. {

  3.    pthread_create(worker, child_index);

  4. }

  5. for(children)

  6. {

  7.    pthread_join(child);

  8. }

  9. worker(child_index)

  10. {

  11.    for(i_image = child_index; i_image < N_images; i_image += N_children)

  12.    {

  13.        results = process(images[i_image]);

  14.        flockfile(stdout);

  15.        printf(results);

  16.        funlockfile(stdout);

  17.    }

  18. }

這更簡單,如預期的那樣工作。我猜有時執行緒更好。

將部分讀取的檔案傳遞給子行程

對於各種 vnlog[2] 工具,我需要實現這個操作序列:

☉ 行程開啟一個關閉 O_CLOEXEC 標誌的檔案
☉ 行程讀取此檔案的一部分(在 vnlog 的情況下直到圖例的末尾)
☉ 行程呼叫 exec() 以呼叫另一個程式來處理已經開啟的檔案的其餘部分

第二個程式可能需要命令列中的檔案名而不是已開啟的檔案描述符,因為第二個程式可能自己呼叫 open()。如果我傳遞檔案名,這個新程式將重新開啟檔案,然後從頭開始讀取檔案,而不是從原始程式停止的位置開始讀取。在我的程式上不可以這樣做,因此將檔案名傳遞給第二個程式是行不通的。

所以我真的需要以某種方式傳遞已經開啟的檔案描述符。我在使用 Linux(其他作業系統可能在這裡表現不同),所以我理論上可以透過傳遞 /dev/fd/N 而不是檔案名來實現。但事實證明這也不起作用。在 Linux上(再說一次,也許是特定於 Linux)對於普通檔案 /dev/fd/N 是原始檔案的符號連結。所以這最終做的是與傳遞檔案名完全相同的事情。

但有一個臨時方案!如果我們正在讀取管道而不是檔案,那麼沒有什麼可以符號連結,並且 /dev/fd/N 最終將原始管道傳遞給第二個行程,然後程式正常工作。我可以透過將上面的 open("filename") 更改為 popen("cat filename") 之類的東西來偽裝。呸!這真的是我們所能做到最好的嗎?這在 BSD 上看上去會怎麼樣?


via: http://notes.secretsauce.net/notes/2018/08/03_unix-curiosities.html

作者:Dima Kogan[4] 選題:lujun9972 譯者:geekpi 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖