亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在標準輸出線程上并發寫入是安全的嗎?

在標準輸出線程上并發寫入是安全的嗎?

Go
狐的傳說 2022-07-18 16:54:49
下面的代碼不會引發數據競爭package mainimport (    "fmt"    "os"    "strings")func main() {    x := strings.Repeat(" ", 1024)    go func() {        for {            fmt.Fprintf(os.Stdout, x+"aa\n")        }    }()    go func() {        for {            fmt.Fprintf(os.Stdout, x+"bb\n")        }    }()    go func() {        for {            fmt.Fprintf(os.Stdout, x+"cc\n")        }    }()    go func() {        for {            fmt.Fprintf(os.Stdout, x+"dd\n")        }    }()    <-make(chan bool)}我嘗試了多個長度的數據,變體https://play.golang.org/p/29Cnwqj5K30這篇文章說它不是TS。這封郵件并沒有真正回答這個問題,或者我不明白。os和fmt的包文檔對此沒有提及太多。我承認我沒有挖掘這兩個包的源代碼來尋找進一步的解釋,它們對我來說似乎太復雜了。有哪些建議和參考?
查看完整描述

1 回答

?
幕布斯7119047

TA貢獻1794條經驗 獲得超8個贊

我不確定它是否有資格作為一個明確的答案,但我會嘗試提供一些見解。


包的F*-functionsfmt僅聲明它們采用實現io.Writer接口的類型的值并調用Write它。函數本身對于并發使用是安全的——從某種意義上說,可以同時調用任意數量的函數fmt.Fwhaveter:包本身已經為此做好了準備,但是當同時寫入實現的類型的相同值時io.Writer,問題變得更加復雜因為在 Go 中對接口的支持并沒有說明真正的并發類型。


換句話說,可能允許或不允許并發的真正點被推遲到寫入函數的“作者” fmt。(還應該記住,這些fmt.*Print*函數可以Write在一次調用期間連續多次調用其目的地,而不是那些由 stock 包提供的函數log。)


所以,我們基本上有兩種情況:


的自定義實現io.Writer。

它的股票實現,例如*os.File由 package.json 函數產生的套接字或圍繞套接字的net包裝器。

第一種情況很簡單:無論實現者做了什么。


第二種情況更難:據我了解,Go 標準庫對此的立場(盡管文檔中沒有明確說明)因為它圍繞操作系統提供的“事物”(例如文件描述符和套接字)提供的包裝是合理的“瘦”,因此無論它們實現什么語義,都由運行在特定系統上的 stdlib 代碼傳遞實現。


例如,POSIX要求write(2) 在對常規文件或符號鏈接進行操作時,調用彼此之間是原子的。這意味著,由于Write對包裝文件描述符或套接字的事物的任何調用實際上都會導致目標系統的單個“寫入”系統調用,因此您可以查閱目標操作系統的文檔并了解會發生什么。


請注意,POSIX 只告訴文件系統對象,如果os.Stdout打開到終端(或偽終端)或管道或任何其他支持write(2)系統調用的東西,結果將取決于相關子系統和/或驅動程序實現——例如,來自多個并發調用的數據可能散布在其中,或者其中一個調用或兩者都可能被操作系統失敗——不太可能,但仍然如此。


回到 Go,根據我的收集,以下事實適用于包裝文件描述符和套接字的 Go stdlib 類型:


它們本身可以安全地并發使用(我的意思是,在 Go 級別上)。

它們“映射”Write并Read一對一地調用底層對象——也就是說,一個Write調用永遠不會分成兩個或多個底層系統調用,并且一個Read調用永遠不會從多個底層系統調用的結果中返回“粘合”的數據。(順便說一句,人們偶爾會被這種樸素的行為絆倒——例如,把這個或這個作為例子。)

因此,基本上,當我們考慮這一點時,每次fmt.*Print*調用都可以自由調用Write任意次數,您使用os.Stdout, 的示例將:


永遠不要導致數據競爭——除非你已經為變量分配了os.Stdout一些自定義實現——但是

實際寫入底層 FD 的數據將以不可預知的順序混合,這可能取決于許多因素,包括操作系統內核版本和設置、用于構建程序的 Go 版本、硬件和系統上的負載。

TL;博士


多個并發調用fmt.Fprint*寫入相同的“writer”值將它們的并發性推遲到“writer”的實現(類型)。

在您在問題中提出的設置中,不可能與 Go stdlib 提供的“類文件”對象進行數據競爭。

真正的問題不在于 Go 程序級別的數據競爭,而是在 OS 級別發生的對單個資源的并發訪問。在那里,我們(通常)不談論數據競爭,因為 Go 支持的商品操作系統將人們可能“寫入”的東西暴露為抽象,其中真正的數據競爭可能表明內核或驅動程序中存在錯誤(以及Go 的競爭檢測器無論如何都無法檢測到它,因為該內存不會由為進程提供動力的 Go 運行時擁有)。

基本上,在您的情況下,如果您需要確保任何特定調用生成的數據fmt.Fprint*作為操作系統提供的實際數據接收器的單個連續片段出現,您需要序列化這些調用,因為fmt包不提供關于Write為其導出的函數調用提供的“編寫器”的次數。

序列化可以是外部的(顯式的,即“獲取鎖,調用fmt.Fprint*,釋放鎖”)或內部的——通過os.Stdout將管理鎖的自定義類型包裝并使用它)。當我們在做的時候,log包就是這樣做的,并且可以直接用作它提供的“記錄器”,包括默認的,允許禁止輸出“日志頭”(例如時間戳和文件名)。


查看完整回答
反對 回復 2022-07-18
  • 1 回答
  • 0 關注
  • 204 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號