我一直認為 usingstd::cout << something是執行緒安全的。
對于這個小例子
#include <iostream>
#include <thread>
void f()
{
std::cout << "Hello from f\n";
}
void g()
{
std::cout << "Hello from g\n";
}
int main()
{
std::thread t1(f);
std::thread t2(g);
t1.join();
t2.join();
}
我的期望是兩個輸出的順序是未定義的(實際上這就是我在實踐中觀察到的),但是呼叫operator<<是執行緒安全的。
然而,ThreadSanitizer、DRD 和 Helgrind 似乎都給出了關于訪問 std::__1::ios_base::width(long) 和 std::__1::basic_ios<char, std::__1::char_traits >:: 的各種錯誤填()
在 Compiler Explorer 上,我沒有看到任何錯誤。
在 FreeBSD 13 上,ThreadSanitizer 給了我 3 個警告,上面列出的兩個加上底層 i/o 緩沖區的 malloc/memcpy。
再次在13的FreeBSD,DRD給出4個錯誤,width()和fill()倍兩個用于兩個執行緒。
最后,FreeBSD 13 Helgrind 在執行緒創建中給出了一個與 TLS 相關的已知誤報,fill()以及兩次 ?idth()`。
在 Fedora 34 上
- g 11.2.1 和 ThreadSanitizer 沒有錯誤
- DRD 使用 g 編譯的 exe 在 fwrite 中抱怨 malloc/memcpy
- Helgrind 還抱怨 fwrite 以及 , 的構造
cout,再次使用 g 編譯的 exe - clang 12 ThreadSanitizer 抱怨
fill()和width() - DRD與鐺 編譯EXE抱怨
fill(),width(),fwrite和另外一個在start_thread - 帶有 clang exe 的 Helgrind 抱怨一些 TLS,
fill(),width(),fwrite
macOS XCode clang ThreadSanitizer 也會生成警告(將是 libc )。
查看 libc 和 libstdc 代碼,我根本看不到任何保護width(). 所以我不明白為什么沒有對編譯器資源管理器的抱怨。
我嘗試使用 TSAN_OPTIONS=print_suppressions=1 運行,但沒有更多輸出(g Fedora ThreadSanitizer)
似乎確實對width()和fill()呼叫達成了一些共識。
更仔細地查看 libstdc 源代碼,我發現有(有一些修剪和注釋):
// ostream_insert.h
// __n is the length of the string pointed to by __s
template<typename _CharT, typename _Traits>
basic_ostream<_CharT, _Traits>&
__ostream_insert(basic_ostream<_CharT, _Traits>& __out,
const _CharT* __s, streamsize __n)
{
typedef basic_ostream<_CharT, _Traits> __ostream_type;
typedef typename __ostream_type::ios_base __ios_base;
typename __ostream_type::sentry __cerb(__out);
if (__cerb)
{
__try
{
const streamsize __w = __out.width();
if (__w > __n)
{
// snipped
// handle padding
}
else
__ostream_write(__out, __s, __n);
// why no hazard here?
__out.width(0);
}
__out是流物件,cout在這種情況下是全域的。我看不到鎖或原子之類的東西。
關于 ThreadSanitizer/g 如何獲得“干凈”輸出的任何建議?
有一個有點神秘的評論
template<typename _CharT, typename _Traits>
basic_ostream<_CharT, _Traits>::sentry::
sentry(basic_ostream<_CharT, _Traits>& __os)
: _M_ok(false), _M_os(__os)
{
// XXX MT
if (__os.tie() && __os.good())
__os.tie()->flush();
libc 代碼看起來很相似。在iostream
template<class _CharT, class _Traits>
basic_ostream<_CharT, _Traits>&
__put_character_sequence(basic_ostream<_CharT, _Traits>& __os,
const _CharT* __str, size_t __len)
{
#ifndef _LIBCPP_NO_EXCEPTIONS
try
{
#endif // _LIBCPP_NO_EXCEPTIONS
typename basic_ostream<_CharT, _Traits>::sentry __s(__os);
if (__s)
{
typedef ostreambuf_iterator<_CharT, _Traits> _Ip;
if (__pad_and_output(_Ip(__os),
__str,
(__os.flags() & ios_base::adjustfield) == ios_base::left ?
__str __len :
__str,
__str __len,
__os,
__os.fill()).failed())
__os.setstate(ios_base::badbit | ios_base::failbit);
并在 locale
template <class _CharT, class _OutputIterator>
_LIBCPP_HIDDEN
_OutputIterator
__pad_and_output(_OutputIterator __s,
const _CharT* __ob, const _CharT* __op, const _CharT* __oe,
ios_base& __iob, _CharT __fl)
{
streamsize __sz = __oe - __ob;
streamsize __ns = __iob.width();
if (__ns > __sz)
__ns -= __sz;
else
__ns = 0;
for (;__ob < __op; __ob, __s)
*__s = *__ob;
for (; __ns; --__ns, __s)
*__s = __fl;
for (; __ob < __oe; __ob, __s)
*__s = *__ob;
__iob.width(0);
return __s;
}
我再次看到沒有執行緒保護,但這次工具也檢測到了危險。
這些是真正的問題嗎?對于普通呼叫,operator<<的值width不會改變,并且始終為 0。
uj5u.com熱心網友回復:
libstdc 不會產生錯誤,而libc 會。
iostream.objects.overview不會導致多執行緒并發訪問同步 ([ios.members.static]) 標準 iostream 物件的格式化和未格式化輸入 ([istream]) 和輸出 ([ostream]) 函式或標準 C 流在資料競爭中([intro.multithread])。
所以這對我來說看起來像是一個 libc 錯誤。
uj5u.com熱心網友回復:
我從 Jonathan Wakely 那里得到了答案。讓我覺得比較傻。
不同之處在于在 Fedora 上,libstdc .so 包含 iostream 類的顯式實體化。libstdc .so 未針對 ThreadSanitizer 進行檢測,因此它無法檢測到任何與其相關的危害。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/371700.html
上一篇:從二維向量獲取輸入時出現分割錯誤
下一篇:CMake無法鏈接自定義庫
