Харт системное программирование в среде windows. Джонсон Харт - Системное программирование в среде Windows

Последнее обновление: 31.10.2015

Нередко в потоках используются некоторые разделяемые ресурсы, общие для всей программы. Это могут быть общие переменные, файлы, другие ресурсы. Например:

Class Program { static int x=0; static void Main(string args) { for (int i = 0; i < 5; i++) { Thread myThread = new Thread(Count); myThread.Name = "Поток " + i.ToString(); myThread.Start(); } Console.ReadLine(); } public static void Count() { x = 1; for (int i = 1; i < 9; i++) { Console.WriteLine("{0}: {1}", Thread.CurrentThread.Name, x); x++; Thread.Sleep(100); } } }

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

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

Class Program { static int x=0; static object locker = new object(); static void Main(string args) { for (int i = 0; i < 5; i++) { Thread myThread = new Thread(Count); myThread.Name = "Поток " + i.ToString(); myThread.Start(); } Console.ReadLine(); } public static void Count() { lock (locker) { x = 1; for (int i = 1; i < 9; i++) { Console.WriteLine("{0}: {1}", Thread.CurrentThread.Name, x); x++; Thread.Sleep(100); } } } }

Для блокировки с ключевым словом lock используется объект-заглушка, в данном случае это переменная locker . Когда выполнение доходит до оператора lock, объект locker блокируется, и на время его блокировки монопольный доступ к блоку кода имеет только один поток. После окончания работы блока кода, объект locker освобождается и становится доступным для других потоков.

Привет! Сегодня продолжим рассматривать особенности многопоточного программирования и поговорим о синхронизации потоков.

Что же такое «синхронизация»? Вне области программирования под этим подразумевается некая настройка, позволяющая двум устройствам или программам работать совместно. Например, смартфон и компьютер можно синхронизировать с Google-аккаунтом, личный кабинет на сайте - с аккаунтами в социальных сетях, чтобы логиниться с их помощью. У синхронизации потоков похожий смысл: это настройка взаимодействия потоков между собой. В предыдущих лекциях наши потоки жили и работали обособленно друг от друга. Один что-то считал, второй спал, третий выводил что-то на консоль, но друг с другом они не взаимодействовали. В реальных программах такие ситуации редки. Несколько потоков могут активно работать, например, с одним и тем же набором данных и что-то в нем менять. Это создает проблемы. Представь, что несколько потоков записывают текст в одно и то же место - например, в текстовый файл или консоль. Этот файл или консоль в данном случае становится общим ресурсом. Потоки не знают о существовании друг друга, поэтому просто записывают все, что успеют за то время, которое планировщик потоков им выделит. В недавней лекции курса у нас был пример, к чему это приведет, давай его вспомним: Причина кроется в том, что потоки работали с общим ресурсом, консолью, не согласовывая действия друг с другом. Если планировщик потоков выделил время Потоку-1, тот моментально пишет все в консоль. Что там уже успели или не успели написать другие потоки - неважно. Результат, как видишь, плачевный. Поэтому в многопоточном программировании ввели специальное понятие мьютекс (от англ. «mutex», «mutual exclusion» - «взаимное исключение») . Задача мьютекса - обеспечить такой механизм, чтобы доступ к объекту в определенное время был только у одного потока. Если Поток-1 захватил мьютекс объекта А, остальные потоки не получат к нему доступ, чтобы что-то в нем менять. До тех пор, пока мьютекс объекта А не освободится, остальные потоки будут вынуждены ждать. Пример из жизни: представь, что ты и еще 10 незнакомых людей участвуете в тренинге. Вам нужно поочередно высказывать идеи и что-то обсуждать. Но, поскольку друг друга вы видите впервые, чтобы постоянно не перебивать друг друга и не скатываться в гвалт, вы используете правило c «говорящим мячиком»: говорить может только один человек - тот, у кого в руках мячик. Так дискуссия получается адекватной и плодотворной. Так вот, мьютекс, по сути, и есть такой мячик. Если мьютекс объекта находится в руках одного потока, другие потоки не смогут получить доступ к работе с этим объектом. Не нужно ничего делать, чтобы создать мьютекс: он уже встроен в класс Object , а значит, есть у каждого объекта в Java.

Как работает оператор synchronized

Давай познакомимся с новым ключевым словом - synchronized . Им помечается определенный кусок нашего кода. Если блок кода помечен ключевым словом synchronized , это значит, что блок может выполняться только одним потоком одновременно. Синхронизацию можно реализовать по-разному. Например, создать целый синхронизированный метод: public synchronized void doSomething () { //...логика метода } Или же написать блок кода, где синхронизация осуществляется по какому-то объекту: public class Main { private Object obj = new Object () ; public void doSomething () { synchronized (obj) { } } } Смысл прост. Если один поток зашел внутрь блока кода, который помечен словом synchronized , он моментально захватывает мьютекс объекта, и все другие потоки, которые попытаются зайти в этот же блок или метод вынуждены ждать, пока предыдущий поток не завершит свою работу и не освободит монитор. Кстати! В лекциях курса ты уже видел примеры synchronized , но они выглядели иначе: public void swap () { synchronized (this ) { //...логика метода } } Тема для тебя новая, и путаница с синтаксисом, само собой, первое время будет. Поэтому запомни сразу, чтобы не путаться потом в способах написания. Эти два способа записи означают одно и то же: public void swap () { synchronized (this ) { //...логика метода } } public synchronized void swap () { } } В первом случае создаешь синхронизированный блок кода сразу же при входе в метод. Он синхронизируется по объекту this , то есть по текущему объекту. А во втором примере вешаешь слово synchronized на весь метод. Тут уже нет нужды явно указывать какой-то объект, по которому осуществляется синхронизация. Раз словом помечен целый метод, этот метод автоматически будет синхронизированным для всех объектов класса. Не будем углубляться в рассуждения, какой способ лучше. Пока выбирай то, что больше нравится:) Главное - помни: объявить метод синхронизированным можно только тогда, когда вся логика внутри него выполняется одним потоком одновременно. Например, в этом случае сделать метод doSomething() синхронизированным будет ошибкой: public class Main { private Object obj = new Object () ; public void doSomething () { //...какая-то логика, доступная для всех потоков synchronized (obj) { //логика, которая одновременно доступна только для одного потока } } } Как видишь, кусочек метода содержит логику, для которой синхронизация не обязательна. Код в нем могут выполнять несколько потоков одновременно, а все критически важные места выделены в отдельный блок synchronized . И еще один момент. Давай рассмотрим «под микроскопом» наш пример из лекции с обменом именами: public void swap () { synchronized (this ) { //...логика метода } } Обрати внимание: синхронизация проводится по this . То есть по конкретному объекту MyClass . Представь, что у нас есть 2 потока (Thread-1 и Thread-2) и всего один объект MyClass myClass . В этом случае, если Thread-1 вызовет метод myClass.swap() , мьютекс объекта будет занят, и Thread-2 при попытке вызвать myClass.swap() повиснет в ожидании, когда мьютекс освободится. Если же у нас будет 2 потока и 2 объекта MyClass - myClass1 и myClass2 - на разных объектах наши потоки спокойно смогут одновременно выполнять синхронизированные методы. Первый поток выполняет: myClass1. swap () ; Второй выполняет: myClass2. swap () ; В этом случае ключевое слово synchronized внутри метода swap() не повлияет на работу программы, поскольку синхронизация осуществляется по конкретному объекту. А в последнем случае объектов у нас 2. Поэтому потоки не создают друг другу проблем. Ведь у двух объектов есть 2 разных мьютекса, и их захват не зависит друг от друга .

Особенности синхронизации в статических методах

А что делать, если нужно синхронизировать статический метод ? class MyClass { private static String name1 = "Оля" ; private static String name2 = "Лена" ; public static synchronized void swap () { String s = name1; name1 = name2; name2 = s; } } Непонятно, что будет выполнять роль мьютекса в этом случае. Ведь мы уже определились, что у каждого объекта есть мьютекс. Но проблема в том, что для вызова статического метода MyClass.swap() нам не нужны объекты: метод-то статический! И что дальше? :/ На самом деле, проблемы в этом нет. Создатели Java обо всем позаботились:) Если метод, в котором содержится критически важная «многопоточная» логика, статический, синхронизация будет осуществляться по классу. Для большей ясности, приведенный выше код можно переписать так: class MyClass { private static String name1 = "Оля" ; private static String name2 = "Лена" ; public static void swap () { synchronized (MyClass. class ) { String s = name1; name1 = name2; name2 = s; } } } В принципе, ты мог до этого додуматься самостоятельно: раз объектов нет, значит механизм синхронизации должен быть как-то «зашит» в сами классы. Так оно и есть: по классам тоже можно синхронизироваться.

Джонсон М. Харт Системное программирование в среде Windows Третье издание

Введение

В этой книге описывается разработка приложений с использованием интерфейса прикладного программирования (Application Programming Interface, API) операционных систем Windows компании Microsoft, причем основное внимание уделяется базовым системным службам, включая управление файловой системой, процессами и потоками, межпроцессное взаимодействие, сетевое программирование и синхронизацию. Пользовательские интерфейсы, внутренние функции Windows и драйверы ввода/вывода в данной книге не рассматриваются, хотя сами по себе эти темы не менее важны и представляют не меньший интерес. Для примеров преимущественно выбирались реалистичные сценарии, и поэтому многие из них вполне могут служить в качестве основы для построения реальных приложений.

Win32/Win64 API, или обобщенно Windows API, поддерживаются семейством 32– и 64-разрядных операционных систем компании Microsoft, в которое в настоящее время входят Windows XP, Windows 2000 и Windows Server 2003. К числу ранних представителей этого семейства относятся операционные системы Windows NT, Windows Me, Windows 98 и Windows 95; в настоящее время эти системы считаются устаревшими, однако многие из приведенных в книге примеров программ способны выполняться и под их управлением. Вопросы перехода от платформы Win32 к развивающейся платформе Win64 обсуждаются по мере необходимости. Win64, поддерживаемый в качестве 64-разрядного интерфейса в некоторых версиях Windows Server 2003 и Windows XP, почти идентичен Win32.

Не вызывает сомнений, что Windows API является важнейшим фактором, который оказывает влияние на весь процесс разработки приложений, и во многих случаях вытесняет поддерживаемый операционными системами UNIX и Linux POSIX API, поскольку считается более предпочтительным или, по крайней мере, предоставляющим те же возможности для приложений, ориентированных на настольные и серверные системы. Поэтому многие опытные программисты заинтересованы в скорейшем изучении Windows API, и данная книга призвана содействовать этому.

Прежде всего, необходимо рассказать вам о том, что представляет собой Windows API, и показать, как им пользоваться в реальных ситуациях, причем этот рассказ должен быть как можно более кратким и не перегруженным излишними деталями. Поэтому данная книга предназначена не для использования в качестве справочного руководства, а для ознакомления с основными свойствами наиболее важных функций и демонстрации возможностей их применения в ситуациях практического программирования. Вооружившись этими знаниями, читатель сможет воспользоваться обширной справочной документацией, предоставляемой компанией Microsoft, для самостоятельного углубленного изучения отдельных вопросов, расширенных возможностей и менее приметных функций в соответствии с возникшими потребностями или заинтересованностью. Лично мне при таком подходе изучение Windows API далось легко, а разработка Windows-программ доставила огромное удовольствие, хотя и без неприятных минут также не обошлось. Мои порывы энтузиазма легко просматриваются в некоторых местах книги, что, собственно, и неудивительно. Впрочем, это вовсе не свидетельствует о том, что я безоговорочно соглашусь с превосходством Windows API над API других операционных систем (ОС), но относительно того, что у него есть масса положительных качеств, вряд ли кто-либо станет возражать.

Авторы многих книг, посвященных Windows, значительное внимание уделяют объяснению того, что представляют собой процессы, виртуальная память, межпроцессное взаимодействие, вытесняющий планировщик, но при этом не показывают, как все это используется в реальных ситуациях. Программистам, имеющим опыт работы с системами UNIX, Linux, IBM MVS, Open VMS и некоторыми другими ОС эти понятия уже знакомы, и они заинтересованы лишь в том, чтобы как можно быстрее перейти к изучению того, как эти возможности реализованы в Windows. К тому же, в большинстве книг по Windows важное место отводится методам программирования на основе пользовательского интерфейса. С целью концентрации внимания лишь на самых главных базовых возможностях, предоставляемых системой, в данной книге тема пользовательского интерфейса не затрагивается, и мы ограничиваемся обсуждением лишь простого консольного символьного ввода/вывода.

В соответствии с принятой в данной книге точке зрения Windows - это всего лишь API операционной системы, предоставляющий набор вполне понятных средств. Потребность в ускоренном изучении Windows испытывают многие программисты, независимо от уровня их опыта, и без знания Windows немыслимо обсуждение таких, например, тем, как модель компонентного объекта (Component Object Model, СОМ), разработанная компанией Microsoft. В некоторых отношениях системы Windows превосходят остальные системы, в других - отстают от них или находятся примерно на том же уровне. Задача данной книги состоит в том, чтобы продемонстрировать, как эффективнее всего использовать эти возможности в реальных ситуациях для разработки полезных, высококачественных и высокопроизводительных приложений.

Джонсон М. Харт

Системное программирование в среде Windows

Третье издание

Введение

В этой книге описывается разработка приложений с использованием интерфейса прикладного программирования (Application Programming Interface, API) операционных систем Windows компании Microsoft, причем основное внимание уделяется базовым системным службам, включая управление файловой системой, процессами и потоками, межпроцессное взаимодействие, сетевое программирование и синхронизацию. Пользовательские интерфейсы, внутренние функции Windows и драйверы ввода/вывода в данной книге не рассматриваются, хотя сами по себе эти темы не менее важны и представляют не меньший интерес. Для примеров преимущественно выбирались реалистичные сценарии, и поэтому многие из них вполне могут служить в качестве основы для построения реальных приложений.

Win32/Win64 API, или обобщенно Windows API, поддерживаются семейством 32– и 64-разрядных операционных систем компании Microsoft, в которое в настоящее время входят Windows XP, Windows 2000 и Windows Server 2003. К числу ранних представителей этого семейства относятся операционные системы Windows NT, Windows Me, Windows 98 и Windows 95; в настоящее время эти системы считаются устаревшими, однако многие из приведенных в книге примеров программ способны выполняться и под их управлением. Вопросы перехода от платформы Win32 к развивающейся платформе Win64 обсуждаются по мере необходимости. Win64, поддерживаемый в качестве 64-разрядного интерфейса в некоторых версиях Windows Server 2003 и Windows XP, почти идентичен Win32.

Не вызывает сомнений, что Windows API является важнейшим фактором, который оказывает влияние на весь процесс разработки приложений, и во многих случаях вытесняет поддерживаемый операционными системами UNIX и Linux POSIX API, поскольку считается более предпочтительным или, по крайней мере, предоставляющим те же возможности для приложений, ориентированных на настольные и серверные системы. Поэтому многие опытные программисты заинтересованы в скорейшем изучении Windows API, и данная книга призвана содействовать этому.

Прежде всего, необходимо рассказать вам о том, что представляет собой Windows API, и показать, как им пользоваться в реальных ситуациях, причем этот рассказ должен быть как можно более кратким и не перегруженным излишними деталями. Поэтому данная книга предназначена не для использования в качестве справочного руководства, а для ознакомления с основными свойствами наиболее важных функций и демонстрации возможностей их применения в ситуациях практического программирования. Вооружившись этими знаниями, читатель сможет воспользоваться обширной справочной документацией, предоставляемой компанией Microsoft, для самостоятельного углубленного изучения отдельных вопросов, расширенных возможностей и менее приметных функций в соответствии с возникшими потребностями или заинтересованностью. Лично мне при таком подходе изучение Windows API далось легко, а разработка Windows-программ доставила огромное удовольствие, хотя и без неприятных минут также не обошлось. Мои порывы энтузиазма легко просматриваются в некоторых местах книги, что, собственно, и неудивительно. Впрочем, это вовсе не свидетельствует о том, что я безоговорочно соглашусь с превосходством Windows API над API других операционных систем (ОС), но относительно того, что у него есть масса положительных качеств, вряд ли кто-либо станет возражать.

Авторы многих книг, посвященных Windows, значительное внимание уделяют объяснению того, что представляют собой процессы, виртуальная память, межпроцессное взаимодействие, вытесняющий планировщик, но при этом не показывают, как все это используется в реальных ситуациях. Программистам, имеющим опыт работы с системами UNIX, Linux, IBM MVS, Open VMS и некоторыми другими ОС эти понятия уже знакомы, и они заинтересованы лишь в том, чтобы как можно быстрее перейти к изучению того, как эти возможности реализованы в Windows. К тому же, в большинстве книг по Windows важное место отводится методам программирования на основе пользовательского интерфейса. С целью концентрации внимания лишь на самых главных базовых возможностях, предоставляемых системой, в данной книге тема пользовательского интерфейса не затрагивается, и мы ограничиваемся обсуждением лишь простого консольного символьного ввода/вывода.

В соответствии с принятой в данной книге точке зрения Windows - это всего лишь API операционной системы, предоставляющий набор вполне понятных средств. Потребность в ускоренном изучении Windows испытывают многие программисты, независимо от уровня их опыта, и без знания Windows немыслимо обсуждение таких, например, тем, как модель компонентного объекта (Component Object Model, СОМ), разработанная компанией Microsoft. В некоторых отношениях системы Windows превосходят остальные системы, в других - отстают от них или находятся примерно на том же уровне. Задача данной книги состоит в том, чтобы продемонстрировать, как эффективнее всего использовать эти возможности в реальных ситуациях для разработки полезных, высококачественных и высокопроизводительных приложений.

Потенциальная аудитория

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

Программисты и специалисты по разработке программного обеспечения, перед которыми стоит задача переноса существующих приложений, написанных, в частности, для UNIX, на любую из платформ Windows. В книге демонстрируются сравнительные возможности функций и моделей программирования, связанных с использованием Windows, UNIX и стандартной библиотеки С. Каждая из обычных функциональных возможностей UNIX, включая управление процессами, синхронизацию, файловые системы и межпроцессное взаимодействие, рассматривается в терминах Windows.

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

Программисты, использующие СОМ и.NET Framework, которые найдут здесь массу полезной информации, облегчающей изучение принципов работы динамически подключаемых библиотек (dynamic link libraries, DLL), моделей потоков и способов их применения, интерфейсов и синхронизации.

Студенты, изучающие компьютерные дисциплины на старших курсах вузов или занятые подготовкой дипломных работ, связанных с системным программированием или разработкой приложений. Книга будет полезна также тем, кто изучает многопоточное программирование или сталкивается с необходимостью создания сетевых приложений. Ее также можно использовать в качестве полезного дополнения к таким, например, источникам, как книга У. Ричарда Стивенса (W. Richard Stevens) Advanced Programming in the UNIX Environment (см. библиографию), что позволит студентам сравнить возможности Windows и UNIX. Эта книга будет хорошим подспорьем и для студентов, проходящих курс ОС, поскольку в ней показано, какими именно средствами обеспечивается базовая функциональность ОС, представляющих интерес в коммерческом отношении.

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

Изменения в третьем издании

Наряду со значительным обновлением и реорганизацией по сравнению с первыми двумя изданиями, в третьем издании добавлен обширный объем нового материала. Это издание призвано решать следующие задачи:

Охватить новые возможности, появившиеся в Windows XP, Windows 2000 и Windows Server 2003, а также рассмотреть вопросы перехода к платформе Win64.

Исключить материал, учитывающий специфику ОС Windows 95, Windows 98 и Windows ME (семейство "Windows 9x"), как устаревший, поскольку на поставляемых в настоящее время персональных системах устанавливается Windows XP, и ограничения, свойственные Windows 9х, уже потеряли свою актуальность. В примерах программ без каких бы то ни было оговорок используются средства, которые входят лишь в текущие версии Windows, хотя в результате этого в Windows 9x некоторые программы работать не будут.

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

Подчеркнуть все возрастающее влияние Windows 2000 и Windows Server 2003 и входящих в их состав новых средств на возможности высокопроизводительных, масштабируемых, многопоточных серверных приложений.

Исследовать зависимость производительности программ от принципов их построения, обратив особое внимание на многопоточные программы с синхронизацией и на особенности эксплуатации этих программ в условиях симметричных многопроцессорных (Symmetrical Multiprocessor, SMP) систем.



Просмотров