У меня есть класс, который не является потокобезопасным:
class Foo {
/* Abstract base class, code which is not thread safe */
};
Более того, если у вас есть объекты foo1 и foo2, вы не можете вызывать foo1-> someFunc () до тех пор, пока foo2-> anotherFunc () не вернется (это может произойти с двумя потоками). Это ситуация, и ее нельзя изменить (подкласс Foo фактически является оболочкой для скрипта python).
Чтобы предотвратить нежелательные звонки, я создал следующее -
class FooWrapper {
public:
FooWrapper(Foo* foo, FooWrappersMutex* mutex);
/* Wrapped functions from Foo */
};
Внутри FooWrapper оборачивает вызовы функций Foo с помощью общего мьютекса.
Я хочу протестировать FooWrapper на безопасность потоков. Моя самая большая проблема заключается в том, что потоки управляются операционной системой, а это означает, что у меня меньше контроля над их выполнением. Я хотел бы протестировать следующий сценарий:
Как проще всего протестировать такой сценарий автоматически?
Я использую QT на Win32, хотя я бы предпочел решение, по крайней мере, кроссплатформенное, как и QT.





Пока что я написал следующий код. Иногда это работает, а иногда тест терпит неудачу, поскольку спящего режима недостаточно для запуска всех потоков.
//! Give some time to the other threads
static void YieldThread()
{
#ifdef _WIN32
Sleep(10);
#endif //_WIN32
}
class FooWithMutex: public Foo {
public:
QMutex m_mutex;
virtual void someFunc()
{
QMutexLocker(&m_mutex);
}
virtual void anotherFunc()
{
QMutexLocker(&m_mutex);
}
};
class ThreadThatCallsFooFunc1: public QThread {
public:
ThreadThatCallsFooFunc1( FooWrapper& fooWrapper )
: m_fooWrapper(fooWrapper) {}
virtual void run()
{
m_fooWrapper.someFunc();
}
private:
FooWrapper& m_fooWrapper;
};
class ThreadThatCallsFooFunc2: public QThread {
public:
ThreadThatCallsFooFunc2( FooWrapper& fooWrapper )
: m_fooWrapper(fooWrapper) {}
virtual void run()
{
m_fooWrapper.anotherFunc();
}
private:
FooWrapper& m_fooWrapper;
};
TEST(ScriptPluginWrapperTest, CallsFromMultipleThreads)
{
// FooWithMutex inherits the abstract Foo and adds
// mutex lock/unlock on each function.
FooWithMutex fooWithMutex;
FooWrapper fooWrapper( &fooWithMutex );
ThreadThatCallsFooFunc1 thread1(fooWrapper);
ThreadThatCallsFooFunc2 thread2(fooWrapper);
fooWithMutex.m_mutex.lock();
thread1.start(); // Should block
YieldThread();
ASSERT_FALSE( thread1.isFinished() );
thread2.start(); // Should finish immediately
YieldThread();
ASSERT_TRUE( thread2.isFinished() );
fooWithMutex.m_mutex.unlock();
YieldThread();
EXPECT_TRUE( thread1.isFinished() );
}
Возможно, вы захотите проверить ШАХМАТЫ: инструмент для систематического тестирования параллельного программного обеспечения от Microsoft Research. Это среда тестирования многопоточных программ (как .NET, так и машинного кода).
Если я правильно понял, он заменяет библиотеки потоков операционной системы своими собственными, чтобы управлять переключением потоков. Затем он анализирует программу, чтобы выяснить все возможные способы, которыми потоки выполнения потоков могут чередоваться, и повторно запускает набор тестов для каждого возможного чередования.
Вместо того, чтобы просто проверять, завершен ли конкретный поток или нет, почему бы не создать поддельный Foo, который будет вызываться вашей оболочкой, в которой функции записывают время, в которое они были фактически запущены / завершены. Тогда вашему потоку yield нужно только подождать достаточно долго, чтобы можно было различить разницу между записанными временами. В своем тесте вы можете утверждать, что время начала another_func находится после времени начала some_func, а время завершения - до времени завершения some_func. Поскольку ваш поддельный класс записывает только время, этого должно быть достаточно, чтобы гарантировать правильную работу класса-оболочки.
РЕДАКТИРОВАТЬ: Вы, конечно, знаете, что то, что делает ваш объект Foo, может быть антипаттерн, а именно Последовательная связь. В зависимости от того, что он делает, вы можете справиться с этим, просто заставив второй метод ничего не делать, если первый метод еще не был вызван. Используя пример из ссылки Sequential Coupling, это будет похоже на то, что автомобиль ничего не делает при нажатии педали акселератора, если автомобиль еще не был запущен. Если ничего не предпринимать, вы можете подождать и попробовать еще раз позже, инициировать «последовательность запуска» в текущем потоке или обработать ее как ошибку. Все эти вещи могут быть реализованы и вашей оболочкой, и их, вероятно, будет легче протестировать.
Вам также может потребоваться быть осторожным, чтобы убедиться, что один и тот же метод не вызывается дважды подряд, если требуется промежуточный вызов другого метода.
Когда вы начинаете многопоточность, ваш код по определению становится недетерминированным, поэтому тестирование безопасности потоков в общем случае невозможно.
Но что касается вашего очень конкретного вопроса, если вы вставляете длинные задержки внутри Foo, чтобы каждый метод Foo занимал определенное время, тогда вы можете делать то, что просите. То есть вероятность того, что первый поток вернется до того, как второй поток войдет в вызов, становится практически нулевой.
Но чего вы на самом деле пытаетесь достичь? Что должен проверить этот тест? Если вы пытаетесь проверить правильность работы класса FooWrappersMutex, этого не произойдет.
Если я правильно помню, инструмент проверяет ваш код на теоретически возможные гонки данных. Дело в том, что вам не нужно запускать свой код, чтобы проверить, правильный он или нет.
Джинкс спешит на помощь