Конспект лекций по предмету "Программирование"


Проблемы многопоточности

Многопоточность – весьма сложная, еще не полностью изученная и, тем более, не полностью формализованная область, в которой имеется много интересных проблем. Рассмотрим некоторые из них.
Семантика системных вызовов 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 ", 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).


Не сдавайте скачаную работу преподавателю!
Данный конспект лекций Вы можете использовать для создания шпаргалок и подготовки к экзаменам.

Поделись с друзьями, за репост + 100 мильонов к студенческой карме :

Пишем конспект самостоятельно:
! Как написать конспект Как правильно подойти к написанию чтобы быстро и информативно все зафиксировать.