C++ 使用全局变量理解多线程

问题描述 投票:0回答:5

我有一个 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;
}
// ...

还节省线程?

我的最后一个问题:我的程序当前仅使用一个互斥体来防止两个线程(相同的函数!)同时运行。我没有对全局变量使用互斥体。这种“情况”是否会导致应用程序崩溃(模块:“ntdll.dll”),异常代码为 0xc0000005?

c++ multithreading mutex
5个回答
15
投票

多次读取始终是线程安全的。一旦一个线程正在写入非原子变量

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 "";
    }
}

7
投票

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

不。绝不。如果您仅从多个线程读取数据,那么您始终是安全的。

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

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

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

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

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


2
投票

如果两个线程reading相同,会出现应用程序崩溃吗 多变的?

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

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

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

此外:是这一部分吗

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

还节省线程?

不。您的互斥体

m
仅保护局部变量
value
,由于它是本地变量,因此无需保护。但随后您释放互斥体并复制(read)全局
var
变量,而另一个线程可能会写入它。为了使其线程安全,可以使用
std::lock_guard
,然后您就不需要手动锁定/解锁互斥体。或将代码更新为:

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

我没有对全局变量使用互斥体。难道是这个 “情况”可能导致应用程序崩溃

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


0
投票

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

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

没有。

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

没有。

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


0
投票

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

提出的解决方案仍然不太正确。

var 在互斥体之外读取,并且可以在那时修改。

这看起来像 C++11。如果

std::string
使用共享字符串(C++11 中禁止),则可能会导致读取问题。

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

© www.soinside.com 2019 - 2024. All rights reserved.