C++ 理解多线程与全局变量

C++ understanding multithreading with global variables

我有一个声明了一些全局变量的 C++ 程序。之后它分裂成几个线程来完成几个任务。 这些线程读取和写入其中一些全局变量。

如果两个线程读取同一个变量,会不会出现应用程序崩溃?或者仅当一个线程 写入另一个线程当前正在读取的变量时才会出现应用程序崩溃?

那么如果我的第二个问题的答案是肯定的,那么下面的代码示例能解决这个问题吗?

#include <string>
#include <thread>
#include <mutex>
using namespace std;

mutex m;
string var = "foo";

// function to provide read and write access
// "protected" with mutex
string test(string value = "")
{
    m.lock();
    if (value == "")
    {
        m.unlock();
        return var;
    }
    else
    {
        var = value;
        m.unlock();
        return "";
    }
}

void thread1()
{
    // use global variable local
    string localVar = test();
}
void thread2()
{
    // overwrite global variable
    test("bar");
}
void thread3()
{
    // use global variable local
    string localVar = test();
}

int main()
{    
    thread t1(thread1);
    thread t2(thread2);
    thread t3(thread3);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

此外:这部分是

// ...
if (value == "")
{
    m.unlock();
    return var;
}
// ...

也节省线程?

我的最后一个问题:我的程序目前只使用一个互斥量来防止两个线程(相同的函数!)同时 运行。我没有为我的全局变量使用互斥锁。难道这个 "situation" 会导致应用程序崩溃(模块:"ntdll.dll"),异常代码为 0xc0000005?

提前致谢!

简单的问题,简单的答案:

如果两个线程正在读取同一个变量,是否会出现应用程序崩溃?

没有

仅当一个线程写入另一个线程当前正在读取的变量时才会出现应用程序崩溃吗?

没有

然而,您确实应该使用锁和互斥锁等来确保您获得程序的预期输出。尽管如果一个线程写入另一个线程正在读取的变量,程序本身不会崩溃,但是您希望读取线程实际读取什么值?写入前的值to/over还是写入后的值?

没有互斥量就不是线程安全的

建议的解决方案仍然不太正确。

var 是在互斥体之外读取的,此时可以修改。

这看起来像 C++11。如果 std::string 使用共享字符串(在 C++11 中被禁止),可能会导致读取问题。

在读+写的情况下,如果指针在复制时被修改,则可能发生c0000005(访问冲突)。

Will there be an app-crash if two threads are reading the same variable?

没有。绝不。如果您只是从多个线程读取数据,那么您总是安全的。

will there be an app-crash only if one thread writes to a variable which another thread is currently reading?

不完全是,但它 可以 导致崩溃,这就是您的代码中发生的情况。就从多个线程同时将应用程序崩溃到 read/write 而言,这不是 "dangerous"。最坏的情况是你在某些地方得到了垃圾值。它本身不会让你的应用程序崩溃,但它最终肯定会导致崩溃。问题是当您正在读取的数据具有原始值(例如整数)以外的含义时。例如,如果您正在读取一个内存地址(指针),然后尝试访问该地址的内存,但内存已经被释放,那么您就有麻烦了——这就是您的代码中发生的情况。字符串的字符已移动到新地址,但您正在尝试读取旧地址。

要解决您的问题,您应该将整个操作包装在锁中,您可以为此使用一个临时变量:

string test(string value = "")
{
    m.lock();
    if (value == "")
    {
        string temp = var;
        m.unlock();
        return temp;
    }
    else
    {
        var = value;
        m.unlock();
        return "";
    }
}

正确的解决方案在保罗的回答中。

多次读取始终是线程安全的。一旦一个线程正在写入非原子变量 var 而其他线程正在从 var 中读取,您就会面临竞争条件的危险。所以你几乎就在那里,但是使用互斥保护(它们是 RAII,因此异常安全和更干净的 C++),比如:

#include <mutex>
#include <string>
// ...
std::mutex m;
std::string var = "foo";
// ...
std::string test(const std::string& value = "")
{
    std::lock_guard<std::mutex> lock(m);
    if (value == "")
    {
        return var;
    }
    else
    {
        var = value;
        return "";
    }
}

Will there be an app-crash if two threads are reading the same variable?

不,你可以安全地同时读取全局变量(如果你知道没有人同时写入它)。读取操作不会修改全局值,因此它保持不变并且所有读者 "see" 相同的值。

Or will there be an app-crash only if one thread writes to a variable which another thread is currently reading?

通常不会,至少不是因为同时进行读写操作。崩溃可能是它的副作用。例如,如果您更新指针值并同时读取它,那么您将尝试访问指针指向的数据。如果读取的值无效,很可能会崩溃。

furthermore: is this part

// ...
if (value == "")
{
    m.unlock();
    return var;
}
// ...

also thread-save?

没有。您的互斥量 m 仅保护局部变量 value ,因为它是本地的,所以不需要保护。但是随后您释放互斥锁并复制(读取)全局var变量,而另一个线程可能会写入它。要使其线程安全,请使用 std::lock_guard 然后您将不需要手动 lock/unlock 互斥体。或将代码更新为:

m.lock();
if (value == "")
{
    string ret_val(var);
    m.unlock();
    return ret_val;
}

I am not using mutexes for my global variables. Could it be that this "situation" can cause an app-crash

正如我之前所写,是的,作为副作用,您的应用程序可能会崩溃。