![]() Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Проблемы многопоточности
Многопоточность – весьма сложная, еще не полностью изученная и, тем более, не полностью формализованная область, в которой имеется много интересных проблем. Рассмотрим некоторые из них. Семантика системных вызовов fork() и exec(). Как уже отмечалось, вклассической ОС UNIX системный вызов fork создает новый " тяжеловесный" процесс со своим адресным пространством, что значительно " дороже", чем создание потока. Однако, с целью поддержания совместимости программ снизу вверх, приходится сохранять эту семантику, а многопоточность вводить с помощью новых системных вызовов. Прекращение потоков. Важной проблемой является проблема прекращения потоков: например, если родительский поток прекращается, то должен ли при этом прекращаться дочерний поток? Если прекращается стандартный процесс, создавший несколько потоков, то должны ли прекращаться все его потоки? Ответы на эти вопросы в разных ОС неоднозначны. Обработка сигналов. Сигналы в UNIX – низкоуровневый механизм обработки ошибочных ситуаций. Примеры сигналов: SIGSEGV - нарушение сегментации (обращение по неверному адресу, чаще всего по нулевому); SIGKILL – сигнал процессу о выполнении команды kill его уничтожения. Пользователь может определить свою процедуру-обработчик сигнала системным вызовом signal. Проблема в следующем: как распространяются сигналы в многопоточных программах и каким потоком они должны обрабатываться? В большинстве случаев этот вопрос решается следующим образом: сигнал обрабатывается потоком, в котором он сгенерирован, и влияет на исполнение только этого потока. В более современных ОС (например, Windows 2000 и более поздних версиях Windows), основанных на объектно-ориентированной методологии, концепция сигнала заменена более высокоуровневой концепцией исключения (exception). Исключение распространяется по стеку потока в порядке, обратном порядку вызовов методов, и обрабатывается первым из них, в котором система находит подходящий обработчик. Аналогичная схема обработки исключений реализована в Java и в. NET. Группы потоков. В сложных задачах, например, задачах моделирования, при большом числе разнородных потоков, возникает потребность в их структурировании с помощью концепции группы потоков – совокупности потоков, имеющей свое собственное имя, над потоками которой определены групповые операции. Наиболее удачно, с нашей точки зрения, группы потоков реализованы в Java (с помощью класса ThreadGroup). Следует отметить также эффективную реализацию пулов потоков (ThreadPool) в. NET. Локальные данные потока (thread-local storage - TLS) – данные, принадлежащие только определенному потоку и используемые только этим потоком. Необходимость в таких данных очевидна, так как многопоточность – весьма важный метод распараллеливания решения большой задачи, при котором каждый поток работает над решением порученной ему части. Все современные операционные системы и платформы разработки программ поддерживают концепцию локальных данных потока. Синхронизация потоков. Поскольку потоки, как и процессы могут использовать общие ресурсы и реагировать на общие события, необходимы средства их синхронизации. Эти средства подробно рассмотрены позже в данном курсе. Тупики (deadlocks) и их предотвращение. Как и процессы, потоки могут взаимно блокировать друг друга (т.е. может создаться ситуация deadlock), при их неаккуратном программировании. Меры по борьбе с тупиками подробно рассмотрены позже в данном курсе. Потоки POSIX (Pthreads)
В качестве конкретной модели многопоточности рассмотрим потоки POSIX (напомним, что данная аббревиатура расшифровывается как Portable Operating Systems Interface of uniX kind – стандарты для переносимых ОС типа UNIX). Многопоточность в POSIX специфицирована стандартом IEEE 1003.1c, который описывает API для создания и синхронизации потоков. Отметим, что POSIX -стандарт API определяет лишь требуемое поведение библиотеки потоков. Реализация потоков оставляется на усмотрение авторов конкретной POSIX -совместимой библиотеки. POSIX -потоки распространены в ОС типа UNIX, а также поддержаны, с целью совместимости программ, во многих других ОС, например, Solaris и Windows NT. Стандарт POSIX определяет два основных типа данных для потоков: pthread_t – дескриптор потока; pthread_attr_t – набор атрибутов потока. Стандарт POSIX специфицирует следующий набор функций для управления потоками: · pthread_create(): создание потока · pthread_exit(): завершение потока (должна вызываться функцией потока при завершении) · pthread_cancel(): отмена потока · pthread_join(): заблокировать выполнение потока до прекращения другого потока, указанного в вызове функции · pthread_detach(): освободить ресурсы занимаемые потоком (если поток выполняется, то освобождение ресурсов произойдёт после его завершения) · pthread_attr_init(): инициализировать структуру атрибутов потока · pthread_attr_setdetachstate(): указать системе, что после завершения потока она может автоматически освободить ресурсы, занимаемые потоком · pthread_attr_destroy(): освободить память от структуры атрибутов потока (уничтожить дескриптор). Имеются следующие примитивы синхронизации POSIX -потоков с помощью мьютексов (mutexes) – аналогов семафоров – и условных переменных (conditional variables) – оба эти типа объектов для синхронизации подробно рассмотрены позже в данном курсе: · - pthread_mutex_init() – создание мьютекса; · - pthread_mutex_destroy() – уничтожение мьютекса; · - pthread_mutex_lock() – закрытие мьютекса; · - pthread_mutex_trylock() – пробное закрытие мьютекса (если он уже закрыт, вызов игнорируется, и поток не блокируется); · - pthread_mutex_unlock() – открытие мьютекса; · - pthread_cond_init() – создание условной переменной; · - pthread_cond_signal() – разблокировка условной переменной; · - pthread_cond_wait() – ожидание по условной переменной. Рассмотрим пример использования POSIX -потоков на языке Си. #include < stdio.h> #include < stdlib.h> #include < time.h> #include < pthread.h> static void wait_thread(void) { time_t start_time = time(NULL); while (time(NULL) == start_time) { // никаких действий, кроме занятия процессора на время до 1 с. } }
static void *thread_func(void *vptr_args) { int i; for (i = 0; i < 20; i++) { fputs(" b\n", stderr); wait_thread(); } return NULL; } int main(void) { int i; pthread_t thread; if (pthread_create(& thread, NULL, thread_func, NULL)! = 0) { return EXIT_FAILURE; } for (i = 0; i < 20; i++) { puts(" a"); wait_thread(); } if (pthread_join(thread, NULL)! = 0) { return EXIT_FAILURE; } return EXIT_SUCCESS; } Пример иллюстрирует параллельное выполнение основного потока, выдающего в стандартный вывод последовательность букв " a", и дочернего потока, выдающего в стандартный поток ошибок (stderr) последовательность букв " b". Обратите внимание на особенности создания потока (pthread_create), указания его тела (исполняемой процедуры потока thread_func) и ожидания завершения дочернего потока (pthread_join).
|