函数本地静态互斥是线程安全的吗? [英] Are function-local static mutexes thread-safe?

查看:114
本文介绍了函数本地静态互斥是线程安全的吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在下面的程序中,我使用函数局部互斥对象尝试使打印函数线程安全:


$ b b

  #include< iostream> 
#include< chrono>
#include< mutex>
#include< string>
#include< thread>


void print(const std :: string& s)
{
//线程安全?
static std :: mutex mtx;
std :: unique_lock< std :: mutex> lock(mtx);
std :: cout<< s<< std :: endl;
}


int main()
{
std :: thread([&](){for(int i = 0; i < 10; ++ i)print(a+ std :: to_string(i));})。
std :: thread([&](){for(int i = 0; i <10; ++ i)print(b+ std :: to_string(i));})。分离();
std :: thread([&](){for(int i = 0; i <10; ++ i)print(c+ std :: to_string(i));})。分离();
std :: thread([&](){for(int i = 0; i <10; ++ i)print(d+ std :: to_string(i));})。分离();
std :: thread([&](){for(int i = 0; i <10; ++ i)print(e+ std :: to_string(i));})。分离();
std :: this_thread :: sleep_for(std :: chrono :: milliseconds(100));这是安全吗?










$ b

我的疑问来自这个问题

$ p

解决方案

在C ++ 11之前,这是不安全的,除非你的编译器做一些特别保证静态局部变量的初始化方式。



前一段时间,我看了一个类似的问题,我检查了Visual Studio为这种情况生成的程序集。您的打印方法生成的汇编代码的伪代码看起来像这样:

  void print(const std :: string& s)
{
if(!init_check_print_mtx){
init_check_print_mtx = true;
mtx.mutex(); // call mutex()ctt for mtx
}

// ...方法的剩余部分
}

init_check_print_mtx 是特定于此方法的编译器生成的全局变量,它跟踪本地静态是否已初始化。注意,在这个变量保护的一次性初始化块里面,变量在互斥体被初始化之前设置为true。



确保其他线程跳过此方法将跳过初始化程序并使用未初始化的 mtx - 与可能初始化 mtx 不止一次 - 但事实上,这样做允许你避免发生的无限递归问题,如果 std :: mutex()回调打印,这个行为实际上是由标准强制的。



上面的Nemo提到这已被修正(更精确地说,重新指定)在C ++ 11中要求等待对于所有赛车线程,这将使这个安全,但你需要检查自己的编译器的合规性。我没有检查,如果实际上,新规范包括这个保证,但我不会惊讶,因为本地静态几乎没有多线程环境没有这个(除了原始值没有任何检查和设置行为,因为它们直接引用了.data段中已经初始化的位置)。


In the following program I attempt the make the print function thread-safe by using a function-local mutex object:

#include <iostream>
#include <chrono>
#include <mutex>
#include <string>
#include <thread>


void print(const std::string & s)
{    
    // Thread safe?
    static std::mutex mtx;
    std::unique_lock<std::mutex> lock(mtx);
    std::cout <<s << std::endl;
}


int main()
{
    std::thread([&](){ for (int i = 0; i < 10; ++i) print("a" + std::to_string(i)); }).detach();
    std::thread([&](){ for (int i = 0; i < 10; ++i) print("b" + std::to_string(i)); }).detach();
    std::thread([&](){ for (int i = 0; i < 10; ++i) print("c" + std::to_string(i)); }).detach();
    std::thread([&](){ for (int i = 0; i < 10; ++i) print("d" + std::to_string(i)); }).detach();
    std::thread([&](){ for (int i = 0; i < 10; ++i) print("e" + std::to_string(i)); }).detach();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

Is this safe?

My doubts arise from this question, which presents a similar case.

解决方案

Prior to C++11, this is not safe in general, unless your compiler makes some special guarantees about the way in which static locals are initialized.

Some time ago, while looking at a similar issue, I examined the assembly generated by Visual Studio for this case. The pseudocode for the generated assembly code for your print method looked something like this:

void print(const std::string & s)
{    
    if (!init_check_print_mtx) {
        init_check_print_mtx = true;
        mtx.mutex();  // call mutex() ctor for mtx
    }

    // ... rest of method
}

The init_check_print_mtx is a compiler generated global variable specific to this method which tracks whether the local static has been initialized. Note that inside the "one time" initialize block guarded by this variable, that the variable is set to true before the mutex is initialized.

I though this was silly since it ensures that other threads racing into this method will skip the initializer and use a uninitialized mtx - versus the alternative of possibly initializing mtx more than once - but in fact doing it this way allows you to avoid the infinite recursion issue that occurs if std::mutex() were to call back into print, and this behavior is in fact mandated by the standard.

Nemo above mentions that this has been fixed (more precisely, re-specified) in C++11 to require a wait for all racing threads, which would make this safe, but you'll need to check your own compiler for compliance. I didn't check if in fact the new spec includes this guarantee, but I wouldn't be at all surprised given that local statics were pretty much useless in multi-threaded environments without this (except perhaps for primitive values which didn't have any check-and-set behavior because they just referred directly to an already initialized location in the .data segment).

这篇关于函数本地静态互斥是线程安全的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆