В C++ понимание многопоточности с глобальными переменными

у меня есть программа на 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 исключение ?

спасибо заранее!

5 ответов


ИД читает всегда потокобезопасным. Как только один поток записывается в неатомную переменную var в то время как другие потоки читают из var вы находитесь в опасности гонки. Таким образом, вы почти там, но используйте mutex guards (они RAII и, следовательно, исключение safe и cleaner 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 "";
    }
}

будет ли сбой приложения, если два потока чтение одну и ту же переменную?

нет. Никогда. Если Вы читаете только из нескольких потоков, вы всегда в безопасности.

будет ли сбой приложения, только если один поток пишет к переменной, которую в настоящее время читает другой поток?

не совсем, но это can привести к аварии, именно это и происходит в вашем коде. Это не "опасно" с точки зрения сбоя приложения для чтения/записи одновременно из нескольких потоков. Худший случай заключается в том, что вы получаете garbaged значения в некоторых местах. Это самостоятельно не приведет к сбою вашего приложения, но это определенно может привести к этому в конечном итоге. Проблема в том, что данные, которые Вы читаете, имеют значение, отличное от примитивного значения (например, целое число). Например, если Вы читаете адрес памяти (указатель), а затем попытаться доступ к памяти по этому адресу, но память уже освобождена, тогда вы в беде - и вот что происходит в коде. Символы строки переехали на новый адрес, но вы пытаетесь прочитать старый адрес.

Чтобы решить вашу проблему, вы должны обернуть всю работу внутри замка, и вы можете использовать временную переменную для этого:

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

правильное решение находится в ответе Павла.


будет ли приложение-сбой, если два потока чтение то же переменная?

нет вы можете безопасно читать глобальную переменную одновременно (если вы знаете, что никто не пишет в нее одновременно). Операция чтения не изменяет глобальное значение, поэтому оно остается неизменным, и все читатели "видят" одно и то же значение.

или будет сбой приложения, только если один поток пишет в переменной другим потоком в настоящее время читает?

вообще нет, по крайней мере, не из-за одновременного чтения и операции записи. Авария может быть побочным эффектом. Например, если вы обновляете значение указателя и читаете его одновременно, вы пытаетесь получить доступ к данным, на которые указывает указатель. Если значение чтения недопустимо, скорее всего, произойдет сбой.

кроме того: это часть

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

также нить сохранить?

нет. Твой мьютекс!--2--> защищает только локальную переменную value который не нуждается в защите, поскольку он является локальным. Но затем вы выпускаете мьютекс и копируете (читать) мировой var переменной, в то время как другой поток может писать в нее. Чтобы сделать его потокобезопасным, используйте std::lock_guard и тогда вам не понадобится вручную блокировать / разблокировать мьютекс. или обновите код до этого:

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

Я не использую мьютексы для моих глобальных переменных. Может быть, это "положение" может вызвать сбой приложения

как я писал ранее, да, в качестве побочного эффекта ваше приложение может произойти сбой.


простой вопрос, простой ответ:

будет ли сбой приложения, если два потока читают одну и ту же переменную?

нет.

будет ли сбой приложения только в том случае, если один поток записывает переменную, которую другой поток в настоящее время читает?

нет.

однако вы действительно должны использовать блокировки и мьютексы и т. д., чтобы убедиться, что вы получаете ожидаемый результат для программ. Хотя сама программа не будет аварийно завершать работу, если один поток записывает переменная, считываемая другим потоком, какое значение вы хотите, чтобы поток чтения действительно читал? Значение раньше было написано до/или после?


не потокобезопасно без мьютексов

предложенное решение все еще не совсем правильно.

var считывается вне мьютекса и может быть изменен в это время.

Это похоже на C++11. Если std::string используются общие строки (запрещены в C++11), что может вызвать проблемы с чтением.

в случае чтения+записи, то c0000005 (нарушение доступа) может произойти, если указатель был изменен, как он был скопирован.