У меня есть объект приложения, который может получать сообщения от нескольких служб, работающих в нескольких потоках. Сообщение отправляется изнутри экземпляром объекта диспетчера в потоках служб . Приложение может в любой момент изменить текущий диспетчер. Диспетчеры никогда не уничтожаются. Службы никогда не переживают приложения.
Вот пример кода
#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-битных процессорах.
- Уничтожение диспетчеров небезопасно. Как я уже сказал: я никогда не уничтожаю диспетчеров.
- ???