Как написать автоматизированный тест для безопасности потока - PullRequest
7 голосов
/ 27 декабря 2008

У меня есть класс, который не является потокобезопасным:

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 на безопасность потоков. Моя самая большая проблема заключается в том, что потоки управляются операционной системой, что означает, что у меня меньше контроля над их выполнением. Я хотел бы проверить следующий сценарий:

  • Поток 1 вызывает fooWrapper1-> someFunc () и блокирует внутри функции
  • Поток 2 вызывает fooWrapper2-> anotherFunc () и немедленно возвращается (поскольку someFunc () все еще выполняется)
  • Поток 1 завершает выполнение

Как проще всего автоматически протестировать подобный сценарий?

Я использую QT на Win32, хотя я бы предпочел решение, которое по крайней мере кросс-платформенное, как QT.

Ответы [ 6 ]

9 голосов
/ 27 декабря 2008

Возможно, вы захотите проверить ШАХМАТОР: инструмент для систематического тестирования параллельного программного обеспечения от Microsoft Research. Это среда тестирования для многопоточных программ (как .NET, так и собственного кода).

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

5 голосов
/ 27 декабря 2008

Вместо того, чтобы просто проверять, завершен ли конкретный поток или нет, почему бы не создать фальшивку Foo, которая будет вызываться вашей оболочкой, в которой функции записывают время, когда они фактически были запущены / завершены. Тогда ваш поток доходности должен ждать достаточно долго, чтобы можно было различить разницу между записанными временами. В своем тесте вы можете утверждать, что время запуска another_func идет после времени запуска some_func, а время его завершения - раньше времени завершения some_func. Поскольку ваш фальшивый класс записывает только время, этого должно быть достаточно, чтобы гарантировать, что класс-обертка работает правильно.

EDIT : Вы, конечно, знаете, что ваш Foo объект может быть анти-паттерном , а именно последовательной связью . В зависимости от того, что он делает, вы можете справиться с этим, просто заставив второй метод ничего не делать, если первый метод еще не был вызван. Используя пример из ссылки последовательной связи, это было бы похоже на то, что автомобиль ничего не делает, когда педаль акселератора нажата, если автомобиль еще не запущен. Если ничего не делать нецелесообразно, вы можете либо подождать и повторить попытку позже, инициировать «последовательность запуска» в текущем потоке, либо обработать ее как ошибку. Все эти вещи также могут быть реализованы вашей оболочкой, и, вероятно, их будет проще тестировать.

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

3 голосов
/ 27 декабря 2008

Intel Threadchecker .

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

2 голосов
/ 27 декабря 2008

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

Но к вашему очень конкретному вопросу: если вы вставляете длинные задержки в Foo, чтобы каждый метод Foo занимал много времени, тогда вы можете делать то, что просите. То есть вероятность того, что первый поток вернется до того, как второй поток войдет в вызов, станет практически нулевой.

Но что вы действительно пытаетесь достичь? Что этот тест должен проверить? Если вы пытаетесь проверить, что класс FooWrappersMutex работает правильно, это не будет сделано.

0 голосов
/ 23 июля 2011

Джинкс на помощь

http://www.corensic.com/

0 голосов
/ 27 декабря 2008

Пока что я написал следующий код. Иногда это работает, а иногда тест не проходит, поскольку Sleep недостаточно для запуска всех потоков.

//! 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() );
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...