Определение безопасности резьбы в модульных тестах - PullRequest
10 голосов
/ 27 февраля 2012

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

Ответы [ 5 ]

12 голосов
/ 27 февраля 2012

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

Но: мой обычный подход здесь (если у меня есть основания думать, что код, который должен быть потокобезопасным, не таков), состоит в том, чтобы раскрутить много потоков, ожидающих за одним ManualResetEvent. Последний поток, который доберется до шлюза (использует блокировку для подсчета), отвечает за открытие шлюза, так что все потоки попадают в систему одновременно (и уже существуют). Затем они выполняют работу и проверяют наличие нормальных условий выхода. Затем я повторяю этот процесс большое количество раз. Этого обычно достаточно, чтобы воспроизвести предполагаемую гонку нитей и показать, что она переходит от «явно сломанной» к «не сломанной очевидным образом» (что принципиально отличается от «не сломанной»).

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

7 голосов
/ 27 февраля 2012

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

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

Упреждающее модульное тестирование этого кода является потоком Сейф немного сложнее.Не потому, что написать модульный тест сложнее, а потому, что вам нужно провести тщательный анализ, чтобы определить (на самом деле, действительно), что может быть небезопасным для работы с потоками.Если ваш анализ верен, то вы сможете написать тест, который не будет выполнен, пока вы не сделаете поток кода безопасным.

При тестировании на состояние состязания потоков мои тесты почти всегда следуют той же схеме: (этотакое псевдокод)

boolean failed = false;
int iterations = 100;

// threads interact with some object - either 
Thread thread1 = new Thread(new ThreadStart(delegate() {
   for (int i=0; i<iterations; i++) {
     doSomething(); // call unsafe code
     // check that object is not out of synch due to other thread
     if (bad()) {
       failed = true;
     }
   }
});
Thread thread2 = new Thread(new ThreadStart(delegate() {
   for (int i=0; i<iterations; i++) {
     doSomething(); // call unsafe code
     // check that object is not out of synch due to other thread
     if (bad()) {
       failed = true;
     }
   }
});

thread1.start();
thread2.start();
thread1.join();
thread2.join();
Assert.IsFalse(failed, "code was thread safe");
4 голосов
/ 27 февраля 2012

Безопасность потоков - это не то, что вы можете надежно проверить, потому что по своей природе оно недетерминировано. Вы можете попробовать выполнить одну и ту же операцию несколько сотен раз параллельно в разных потоках и посмотреть, согласуются ли результаты в конце. Не очень хорошо, но, наверное, лучше, чем ничего.

3 голосов
/ 28 февраля 2012

Я отказался от юнит-тестов, чтобы попытаться обнаружить проблемы с многопоточностью.Я просто использую тест загрузки системы с полным набором оборудования (если есть оборудование - часто с моими заданиями, uControllers в сетях) и неоправданно большим количеством автоматических клиентов, работающих без изменений.Я оставляю его включенным на неделю, пока занимаюсь другими делами.В конце недели я снимаю нагрузку и проверяю, что осталось.Если он все еще работает, ни один объект не просочился, а память заметно не увеличилась, я отправляю его.

Это столько качества, сколько я могу себе позволить :)

0 голосов
/ 27 мая 2019

У меня была похожая проблема, когда мы нашли ошибки безопасности потоков. Чтобы исправить это, мы должны были доказать это и затем исправить это. Этот квест привел меня на эту страницу, но я не смог найти никакого реального ответа. Как многие из приведенных выше ответов объяснили почему. Но тем не менее я нашел способ, который мог бы помочь другим:

public static async Task<(bool IsSuccess, Exception Error)> RunTaskInParallel(Func<Task> task, int numberOfParallelExecutions = 2)
    {
        var cancellationTokenSource = new CancellationTokenSource();
        Exception error = null;
        int tasksCompletedCount = 0;
        var result = Parallel.For(0, numberOfParallelExecutions, GetParallelLoopOptions(cancellationTokenSource),
                      async index =>
                      {
                          try
                          {
                              await task();
                          }
                          catch (Exception ex)
                          {
                              error = ex;
                              cancellationTokenSource.Cancel();
                          }
                          finally
                          {
                              tasksCompletedCount++;
                          }

                      });

        int spinWaitCount = 0;
        int maxSpinWaitCount = 100;
        while (numberOfParallelExecutions > tasksCompletedCount && error is null && spinWaitCount < maxSpinWaitCount))
        {
            await Task.Delay(TimeSpan.FromMilliseconds(100));
            spinWaitCount++;
        }

        return (error == null, error);
    }

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

Вот как мы это использовали:

int numberOfParallelExecutions = 2;
RunTaskInParallel(() => doSomeThingAsync(), numberOfParallelExecutions);

Надеюсь, это кому-нибудь поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...