Синхронизация потоков

   
На этом шаге мы рассмотрим вопросы, связанные с доступам к данным.

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

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

   
Другой сценарий по обеспечению синхронизации потоков связан с обновлением глобальных данных приложения. Допустим,
в приложении-диспетчере первый поток обновляет некую структуру данных после снятия показаний с прибора, на
котором установлены датчики. Другой поток считывает записанные в структуру данные и выводит их на экран. Теперь
предположим, что этот поток пытается получить показания в тот самый момент, когда структура данных обновляется
первым потоком. Результат - повреждение данных, причем последствия этого могут буть очень серьезными. Чтобы
одновременно только один поток мог читать и модифицировать данные, необходимо обеспечить синхронизацию
доступа потоков к глобальным данным.

   
Одним из способов синхронизации потоков является использование глобального объекта, выполняющего роль посредника
между потоками. В таблице 1 перечислены имеющиеся в MFC классы синхронизации, производные от
базового класса CSyncObject. Они позволяют координировать асинхронные события любого вида.

Таблица 1. MFC-классы синхронизации

Имя Описание
CCriticalSection Разрешает доступ к объекту только одному потоку в пределах текущего процесса
CMutex Разрешает доступ к объекту только одному потоку из любого процесса
CSemaphore Разрешает одновременный доступ к объекту одному или нескольким потокам, число которых не должно превысить заданное максимальное значение
CEvent Оповещает приложение о возникновении события

   
Классы синхронизации, применяемые совместно с классами синхронизации доступа CSingleLock и CMultiLock,
обеспечивают безопасное обращение к глобальным данным и совместный доступ к ресурсам. Рекомендуются
следующие правила работы с этими классами.

  • Инкапсулируйте в классе глобальные данные и функции доступа к ресурсам. Защитите контролируемые данные и
    регулируйте доступ к ним посредством открытых функций.
  • Внутри класса создайте синхронизирующие объекты нужного типа. Чтобы обеспечить обновление глобальных данных
    одновременно только одним потоком - объект CCriticalSection, для оповещения о готовности ресурса получить
    данные - объект CEvent.
  • В функциях-членах, получающих доступ к данным или ресурсам, создайте экземпляр синхронизирующего объекта.
    Применяйте CSingleLock, когда требуется дождаться одного объекта, и CMultiLock - когда имеется
    несколько объектов, используемых одновременно.
  • Перед тем как функция попытается получить доступ к защищенным данным, вызовите функцию-член Lock()
    синхронизирующего объекта. Она ожидает заданное время (в том числе неограниченное), пока ее объект станет
    доступным. Например объект CCriticalSection доступен, если ему удалось обеспечить защищенное и
    исключительное выполнение текущего потока. Доступность события устанавливается вызовом функции CEvent::SetEvent().
  • После того как функция завершила доступ к защищенным данным, вызовите функцию Unlock() объекта доступа или удалите объект.

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

   
Приведенный ниже пример иллюстрирует процедуру обеспечения безопасного доступа к глобальным данным.
Для защиты доступа к объекту CTime, содержащемуся в классе CTimer, применяются объекты
CCriticalSection и CSingleLock.

  • Реализация безопасного многопоточного доступа к данным класса CTimer.
  • В начале файла MainFrm.cpp среди прочих операторов #include поместите строку:
      #include <afxmt.h>
    
  • Добавьте следующую строку в защищенный раздел описания класса CTimer:
      CCriticalSection m_CS;
    


    Рис.1. Добавление переменной m_CS

  • Удалите реализацию функций CTimer::GetTime() и CTimer::SetTime() (не забудьте на их месте оставить точку с запятой).
  • После описания класса CTimer поместите описание функции CTimer::GetTime():
    CTime CTimer::GetTime() 
    {
      CSingleLock csl(&m_CS);
      csl.Lock();
      CTime time = m_time;
      csl.Unlock();
      return time; 
    }
    
  • Добавьте описание функции CTimer::SetTime() после тех строк, которые были введены в предыдущем пункте:
    void CTimer::SetTime(CTime time) 
    {
      CSingleLock csl(&m_CS);
      csl.Lock();
      m_time = time;
      csl.Unlock(); 
    }
    


    Рис.2. Добавление текстов функций

   
Вызов CSingleLock::Unlock() в этих функциях не обязателен - он помещен здесь для соблюдения хорошего стиля программирования.

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

   
Текст измененного приложения можно взять здесь (66,7 Кб).

   
Со следующего шага мы начнем знакомиться с организацией контекстно-зависимой справки.



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

Оставить комментарий