Книга: Программирование мобильных устройств на платформе .NET Compact Framework

Максимально упрощайте многопоточный код и документируйте его для повышения надежности

Максимально упрощайте многопоточный код и документируйте его для повышения надежности

Безопасность многопоточной поддержки — штука хитрая. Если не уделить должное внимание тому, как осуществляется считывание и запись переменных-членов, то может случиться так, что ваше приложение будет пытаться прочитать в одном потоке переменную, запись которой была начата другим потоком, но еще не успела закончиться; "атомарность", то есть неделимость — выполнение за один раз от начала до конца, для большинства операций над данными, находящимися в памяти, не гарантируется, поскольку для записи большинства типов данных требуется выполнение нескольких инструкций микропроцессора. Тот факт, что возникновение проблем подобного рода зависит от временных характеристик выполнения потоков и случается редко, значительно затрудняет их обнаружение, воспроизведение и отладку. Даже ecли гарантирована атомарность доступа к переменным, но при этом было уделено недостаточное внимание тому, как осуществляются вызовы функций-членов классов, то вы можете оказаться в ситуации, когда либо портятся данные, либо программа ведет себя непредсказуемым образом, поскольку соответствующие данные параллельно изменяются алгоритмами, выполняющимися разными потоками; представьте, например два потока, которые одновременно пытаются вставлять и удалять записи в одном и том же связанном списке. Для надежной обработки таких ситуаций необходимо определить "критические разделы" кода; тем самым будет гарантироваться, что любой код, связанный с одним и тем же объектом семафора, сможет выполнять только одним потоком. (В C# это достигается за счет использования оператора lock(объект), а в Visual Basic — с использованием оператора SyncLock(объект). Для получения более подробной информации относительно двух указанных операторов обратитесь к библиотеке справочной документации MSDN.) Ситуацию могут еще более осложнять "зависания", или "взаимоблокировки", когда два потока, выполняющиеся в одно и то же время в разных критических разделах, вызывают код, который должен войти в критический раздел, "принадлежащий" в данный момент другому потоку; при вхождении в критический раздел другого потока будет приостановлено выполнение каждого потока. По этой причине, а также с учетом факторов производительности, чрезмерно вольное использование критических разделов может привести к появлению дополнительных проблем.

Вы могли бы попытаться сделать все свойства и методы своих классов безопасными в отношении многопоточного выполнения, однако осуществить это чрезвычайно трудно с технической точки зрения и расточительно с точки зрения производительности. В конце концов, весь код вашего приложения оказался бы испещренным множеством самых различных критических разделов и бесчисленным количеством всевозможных объектов, используемых в качестве семафоров совместно с критическими разделами. Код такого типа чрезвычайно трудно проектировать и тестировать; кроме того, он характеризуется повышенными накладными расходами, обусловленными необходимостью осуществления проверок, обеспечивающих безопасность многопоточности, и чрезмерно сериализованным выполнением. Ни в .NET Framework, ни в .NET Compact Framework попытки решения этой задачи не делаются; вместо этого в обеих средах используется подход, основанный на тщательном документировании всех возможностей, и явное объявление того, какие операции безопасны в отношении многопоточного выполнения, а какие таковыми не являются. Предполагается, что разработчики внимательно ознакомятся с документацией и будут ею руководствоваться при использовании классов, свойств и методов. Метод класса, не являющийся безопасным в указанном смысле, не должен вызываться для параллельного выполнения из других потоков. Вместо этого, следует либо создать два различных экземпляра класса, либо сериализовать вызов не являющегося безопасным метода, поместив его в критический раздел. Именно таким способом обеспечивается доступ ко всему, что является необходимым и безопасным, а что таковым не является — документируется.

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

// К ДАННОЙ <ПЕРЕМЕННОЙ/СВОЙСТВУ/МЕТОДУ> ДОСТУП ИЗ НЕСКОЛЬКИХ ПОТОКОВ
// ОСУЩЕСТВЛЯТЬСЯ НЕ ДОЛЖЕН!!!
// Предполагается, что этот метод будет использоваться
// <высокоприоритетным/фоновым> потоком для ...

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

Оглавление книги


Генерация: 0.047. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз