46
47 cout << argv[1] << " contains " << FoundCount
<< " files that contains all keywords.» << endl;
48 return(0);
49 }
С помощью мьютексов доступ к разделяемой памяти для чтения или записи данных разрешается получить только одному потоку. Для гарантии безопасности работы функций, определенных пользователем, можно использовать и другие механизмы и методы, которые реализуют одну из моделей PRAM:
• EREW (монопольное чтение и монопольная запись)
• CREW (параллельное чтение и монопольная запись)
• ERCW (монопольное чтение и параллельная запись)
• CRCW (параллельное чтение и параллельная запись)
Мьютексы используются для реализации EREW-алгоритмов, которые рассматриваются в главе 5.
Безопасность использования потоков и библиотек
Климан (Klieman), Шах (Shah) и Смаалдерс (Smaalders) утверждали:
«Функция или набор функций могут сделать поток безопасным или реентерабельным (повторно-входимым), если эти функции могут вызываться не одним, а несколькими потоками без предъявления каких бы то ни было требований к вызывающей части выполнить определенные действия»(1996)
При разработке многопоточного приложения программист должен обеспечить безопасность параллельно выполняемых функций. Мы уже обсуждали безопасность функций, определенных пользователем, но без учета того, что приложение часто вызывает функции из системных библиотек или библиотек, созданных сторонними производителями. Одни такие функции и/или библиотеки безопасны для потоков, а другие — нет. Если функция небезопасна, это означает, что в ней используется хотя бы одна статическая переменная, осуществляется доступ к глобальным данным и/или она не является реентерабельной.
Известно, что статические переменные поддерживают свои значения между вызовами функции. Если некоторая функция содержит статические переменные, то для ее корректного функционирования требуется считывать (и/или изменять) их значения. Если же к такой функции будут обращаться несколько параллельно выполняемых потоков, возникнут условия «гонок». Если функция модифицирует глобальную переменную, то каждый из нескольких потоков, вызывающих функцию, может попытаться модифицировать эту глобальную переменную. Возникновения условий «гонок» также не миновать, если не синхронизировать множество параллельных доступов к глобальной переменной. Например, несколько параллельных потоков могут выполнять функции, которые устанавливают переменную errno.Для некоторых потоков, предположим, эта функция не может выполниться успешно, и переменная errnoустанавливается равной сообщению об ошибке [10], в то время как другие потоки выполняются успешно. Если реализация компилятора не обеспечивает потоковую безопасность поддержки переменной errno,то какое сообщение получит поток при проверке состояния переменной errno?
Блок кода считается реентерабельным, если его невозможно изменить при выполнении. Реентерабельный код исключает возникновение условий «гонок» благодаря отсутствию ссылок на глобальные переменные и модифицируемые статические данные Следовательно, такой код могут совместно использовать несколько параллельных потоков или процессов без риска создать условия «гонок». Стандарт POSIX определяет ряд реентерабельных функций. Их легко узнать по наличию «суффикса» присоединяемого к имени функции. Перечислим некоторые из них:
getgrgid_r()
getgrnam_r()
getpwuid_r()
sterror_r()
strtok_r()
readdir_r()
rand_r()
ttyname_r()
Если функция получает доступ к незащищенным глобальным переменным, содержит статические модифицируемые переменные или нереентерабельна, то такая функция считается небезопасной для потока. Системные библиотеки или библиотеки созданные сторонними производителями, могут иметь различные версии своих стандартных библиотек. Одна версия предназначена для однопоточных приложений, а другая — для многопоточных. Если предполагается разрабатывать многопоточное приложение, программист должен использовать многопоточные версии нужной ему библиотеки. Некоторые среды требуют не компоновки многопоточных приложений с многопоточной версией библиотеки, а лишь определения макросов, что позволяет объявить реентерабельные версии функций. Такое приложение будет затем компилироваться как безопасное для выполнения потоков.
Во всех ситуациях использовать многопоточные версии функций попросту невозможно. В отдельных случаях многопоточные версии конкретных функций недоступны для данного компилятора или среды. Иногда один интерфейс функции не в состоянии сделать ее безопасной. Кроме того, программист может столкнуться с увеличением числа потоков в среде, которая изначально использовала функции, предназначенные для функционирования в однопоточной среде. В таких условиях обычно используются мьютексы. Например, программа имеет три параллельно выполняемых потока. Два из них, thread1 и thread2, параллельно выполняют функцию funcA(), которая не является безопасной для одновременной работы потоков. Третий поток, thread3, выполняет функцию funcB (). Для решения проблемы, связанной с функцией funcA (), возможно, достаточно заключить в защитную оболочку мьютекса доступ к ней со стороны потоков threadl и thread2:
Читать дальше