Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам

Вебинар: Использование статических анализаторов кода при разработке безопасного ПО - 19.12

>
>
>
V720. The 'SuspendThread' function is u…
menu mobile close menu
Проверка проектов
Сообщения PVS-Studio
Диагностики общего назначения (General Analysis, C++)
Диагностики общего назначения (General Analysis, C#)
Диагностики общего назначения (General Analysis, Java)
Микрооптимизации (C++)
Диагностика 64-битных ошибок (Viva64, C++)
Реализовано по запросам пользователей (C++)
Cтандарт MISRA
Стандарт AUTOSAR
Стандарт OWASP (C++)
Стандарт OWASP (C#)
Проблемы при работе анализатора кода
Дополнительная информация
toggle menu Оглавление

V720. The 'SuspendThread' function is usually used when developing a debugger. See documentation for details.

05 Мар 2015

Анализатор обнаружил, что в программе используется функция SuspendThread() или Wow64SuspendThread(). Сам по себе вызов этих функций не является ошибкой. Однако, разработчики часто используют их не по назначению. Из-за этого, программа может вести себя не так, как ожидает программист.

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

Суть проблемы неправильного использования функции SuspendThread() изложена в следующих статьях:

Прочитайте их. Если выяснится, что функция SuspendThread() используется неправильно, то необходимо переписать код. Если всё хорошо, то просто отключите диагностику V720 в настройках анализатора.

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

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

Почему никогда не стоит приостанавливать работу потока

Это практически так же плохо, как уничтожение потока.

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

Рассмотрим такую программу на (вздох) C#:

using System.Threading;
using SC = System.Console;

class Program {
  public static void Main() {
    Thread t = new Thread(new ThreadStart(Program.worker));
    t.Start();
    SC.WriteLine("Press Enter to suspend");
    SC.ReadLine();
    t.Suspend();
    SC.WriteLine("Press Enter to resume");
    SC.ReadLine();
    t.Resume();
  }
  static void worker() {
    for (;;) SC.Write("{0}\r", System.DateTime.Now);
  }
}

Когда вы запускаете эту программу и жмёте Enter, программа зависает. Но если вы измените функцию worker просто на "for(;;) {}", программа будет работать совершенно нормально. Давайте разберёмся, почему так происходит.

Рабочий поток тратит практически всё время своей работы на вызовы System.Console.WriteLine, поэтому, когда вы зовёте Thread.Suspend(), поток с наибольшей вероятностью и находится в System.Console.WriteLine.

Вопрос: Является ли метод System.Console.WriteLine потокобезопасным?

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

Вопрос: Каким образом обычно объекты делают потокобезопасными?

Вопрос: Каков результат приостановки потока в момент выполнения им потокобезопасной операции?

Вопрос: Что случится, если вы, в дальнейшем, попытаетесь обратиться к тому же объекту (в данном случае – к консоли) из другого потока?

Такой результат не специфичен именно для C#. Подобная логика применима как к потоковой модели Win32, так и к любой другой. В Win32, структура кучи процесса является потокобезопасным объектом, и, поскольку сложно что-либо сделать в Win32 без обращений к куче, приостановка потока в Win32 имеет большую вероятность привести к блокировке вашего процесса.

Но тогда зачем вообще появилась функция SuspendThread?

Отладчики используют её для "заморозки" всех потоков процесса в момент, когда происходит его отладка. Отладчики также могут использовать её при остановке всех потоков процесса, кроме одного, чтобы вы могли отлаживать потоки по одному. Это не создаёт взаимоблокировок в отладчике, ведь он является отдельным процессом.

Функция SuspendThread приостанавливает поток, но делает это асинхронно

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

В процессе этих экспериментов, он обнаружил некоторое странное поведение.

LONG lValue;

DWORD CALLBACK IncrementerThread(void *)
{
 while (1) {
  InterlockedIncrement(&lValue);
 }
 return 0;
}

// This is just a test app, so we will abort() if anything
// happens we don't like.

int __cdecl main(int, char **)
{
 DWORD id;
 HANDLE thread = CreateThread(NULL, 0, IncrementerThread, 
                              NULL, 0, &id);
 
 if (thread == NULL) abort();

 while (1) {
  if (SuspendThread(thread) == (DWORD)-1) abort();

  if (InterlockedOr(&lValue, 0) != InterlockedOr(&lValue, 0)) 
  {

    printf("Huh? The variable lValue was modified by a suspended
           thread?\n");
  }

  ResumeThread(thread);
 }
 return 0;
}

Странным здесь является то, что сообщение "Huh?" было распечатано. Разве может приостановленный поток модифицировать переменную? Возможно ли, что InterlockedIncrement начнёт увеличивать значение переменной, затем окажется "замороженным", но каким-то образом всё-же закончит начатое позже?

Но ответ гораздо проще. Функция SuspendThread говорит планировщику заморозить поток, но не ждёт подтверждения от него о том, что "заморозка" уже произошла. На это намекается в документации на SuspendThread, где говорится

Эта функция в основном разработана для использования отладчиками. Она не предназначена для синхронизации потоков.

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

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

Традиционный способ для этого – позвать GetThreadContext, т.к. это требует от ядра прочитать из контекста приостановленного потока, что, в свою очередь, требует сохранения контекста, что и потребует приостановки потока.

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки при работе с многопоточными примитивами (интерфейсами запуска потоков на выполнение, синхронизации и обмена данными между потоками и пр.).

Данная диагностика классифицируется как: