簡體   English   中英

std :: call_once是可重入的還是線程安全的?

[英]Is std::call_once reentrant and thread safe?

std :: call_once是線程安全的,但是它也可以重入嗎?

我使用VS2012(Debug和Release)進行的測試表明,從單個線程遞歸調用std::call_once是可以的,但如果調用是在不同的線程上進行的,則會導致死鎖。 這是std::call_once的已知限制嗎?

#include "stdafx.h"

#include <iostream>
#include <mutex>
#include <thread>

void Foo()
{
    std::cout << "Foo start" << std::endl;

    std::once_flag flag;
    std::call_once( flag, [](){
        std::cout << "Hello World!" << std::endl;
    });

    std::cout << "Foo end" << std::endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    // Single threaded Works
    {
        std::once_flag fooFlag;
        std::call_once( fooFlag, Foo);      
    }

    // Works
    // Threaded version, join outside call_once
    {
        std::once_flag fooFlag;
        std::thread t;
        std::call_once( fooFlag, [&t](){
            t = std::thread(Foo);
            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        }); 
        t.join();
    }

    // Dead locks
    // Threaded version, join inside call_once
    {
        std::once_flag fooFlag;
        std::call_once( fooFlag, [](){
            auto t = std::thread(Foo);
            t.join();
        });     
    }

    return 0;
}

看起來像std:call_once正在鎖定一個靜態互斥鎖,在該函數退出之前它不會被解鎖。 在單線程情況下它可以工作,因為在第二次調用時線程已經有鎖。 在線程版本上,它將阻塞,直到第一個調用退出。

我還注意到,如果將Foo()函數中的std::once_flag標志更改為static ,則仍會發生死鎖。

最接近標准的是17.6.5.8 [reentrancy]

1 - 除非在本標准中明確指定,否則它是實現定義的,可以遞歸地重新輸入標准C ++庫中的哪些函數。

不幸的是, call_once的規范沒有說明它是遞歸的(或者是跨線程遞歸的),並且線程支持庫前導碼也沒有說明這個主題。

也就是說,VC ++實現顯然不是最理想的,特別是因為可以使用condition_variable編寫一個userland版本的call_once

#include <mutex>
#include <condition_variable>

struct once_flag {
  enum { INIT, RUNNING, DONE } state = INIT;
  std::mutex mut;
  std::condition_variable cv;
};
template<typename Callable, typename... Args>
void call_once(once_flag &flag, Callable &&f, Args &&...args)
{
  {
    std::unique_lock<std::mutex> lock(flag.mut);
    while (flag.state == flag.RUNNING) {
      flag.cv.wait(lock);
    }
    if (flag.state == flag.DONE) {
      return;
    }
    flag.state = flag.RUNNING;
  }
  try {
    f(args...);
    {
      std::unique_lock<std::mutex> lock(flag.mut);
      flag.state = flag.DONE;
    }
    flag.cv.notify_all();
  }
  catch (...) {
    {
      std::unique_lock<std::mutex> lock(flag.mut);
      flag.state = flag.INIT;
    }
    flag.cv.notify_one();
    throw;
  }
}

請注意,這是一個細粒度的實現; 也可以編寫一個粗粒度的實現,它在所有一次標志中使用一對互斥和條件變量,但是你需要確保在拋出異常時通知所有等待的線程(例如,libc ++這樣做) 。

為了提高效率,你可以使once_flag::state成為一個原子並使用雙重檢查鎖定; 為簡潔起見,這里省略了。

這里不是“答案”,但至少確認Visual Studio方面的某些內容與您的使用模式有關。

我在coliru.stacked-crooked.com上編譯並運行了這個示例,它似乎按預期執行,沒有任何死鎖。

這是編譯行:

g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

這是輸出:

Foo start
Hello World!
Foo end
Foo start
Hello World!
Foo end
Foo start
Hello World!
Foo end

在我指責庫實現之前,我個人認為我自己的代碼存在問題。

我確實看到Visual Studio的call_once實現有一些類似的問題:

https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&ved=0CDwQFjAC&url=https%3A%2F%2Fconnect.microsoft.com%2FVisualStudio%2Ffeedback%2Fdetails%2F811192%2Fstd-呼叫一旦-掛起&EI = cVw0U-rZBor-2gXCh4HYDw&USG = AFQjCNGDikvgq8YHVzWLC-BqjeOSN-Vm3Q&SIG2 = 7BtB2jH8DeTKEwzAOAf9lw&BVM = bv.63808443,d.b2I及CAD = RJA

http://www.unknownerror.org/Problem/index/1100762158/why-does-this-c-static-singleton-never-stop/

我有興趣知道Debug或Release中是否出現此問題或兩者兼而有之?

畢竟ISO標准確實說call_once是線程安全的!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM