Охотно вводим условие гонки на 64-битном значении указателя - PullRequest
7 голосов
/ 19 октября 2019

У меня есть объект приложения, который может получать сообщения от нескольких служб, работающих в нескольких потоках. Сообщение отправляется изнутри экземпляром объекта диспетчера в потоках служб . Приложение может в любой момент изменить текущий диспетчер. Диспетчеры никогда не уничтожаются. Службы никогда не переживают приложения.

Вот пример кода

#include <iostream>
#include <thread>
#include <atomic>
#include <cstdlib>
#include <functional>

using namespace std;

using Msg = int;

struct Dispatcher
{
    virtual ~Dispatcher() = default;
    virtual void dispatchMessage(Msg msg) = 0;
};

struct DispatcherA : Dispatcher
{
    void dispatchMessage(Msg msg)
    {
        cout << "Thread-safe dispatch of " << msg << " by A" << endl;
    }
};

struct DispatcherB : Dispatcher
{
    void dispatchMessage(Msg msg)
    {
        cout << "Thread-safe dispatch of " << msg << " by B" << endl;
    }
};

struct Application
{
    Application() : curDispatcher(&a) {}
    void sendMessage(Msg msg)
    {
        // race here as this is called (and dereferenced) from many threads
        // and can be changed by the main thread
        curDispatcher->dispatchMessage(msg);
    }

    void changeDispatcher()
    {
        // race her as this is changed but can be dereferenced by many threads
        if (rand() % 2) curDispatcher = &a;
        else curDispatcher = &b;
    }

    atomic_bool running = true;

    Dispatcher* curDispatcher; // race on this
    DispatcherA a;
    DispatcherB b;
};

void service(Application& app, int i) {
    while (app.running) app.sendMessage(i++);
}

int main()
{
    Application app;
    std::thread t1(std::bind(service, std::ref(app), 1));
    std::thread t2(std::bind(service, std::ref(app), 20));

    for (int i = 0; i < 10000; ++i) 
    {
        app.changeDispatcher();
    }
    app.running = false;

    t1.join();
    t2.join();
    return 0;
}

Я знаю, что здесь есть условие гонки. Указатель curDispatcher доступен многим потокам, и он может быть одновременно изменен основным потоком. Это можно исправить, сделав указатель атомарным и явно загружая его при каждом sendMessage вызове.

Я не хочу платить за атомные нагрузки.

Может ли случиться что-то плохоеоб этом?

Вот что я могу придумать:

  • Значение curDispatcher может кэшироваться службой, и оно всегда может вызывать одно и то же, даже если приложениеизменил значение. Я в порядке с этим. Если я перестану быть в порядке с этим, я могу сделать это нестабильным. В любом случае, вновь созданные службы должны быть в порядке.
  • Если это когда-либо будет выполняться на 32-разрядном процессоре, который эмулирует 64-разрядный, записи и чтения указателя не будут атомарными на уровне команд, и это может привести кнедопустимые значения указателя и сбои: я проверяю, что это работает только на 64-битных процессорах.
  • Уничтожение диспетчеров небезопасно. Как я уже сказал: я никогда не уничтожаю диспетчеров.
  • ???
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...