Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учеб Пособ_Гончаровский.doc
Скачиваний:
1317
Добавлен:
29.03.2015
Размер:
3.65 Mб
Скачать

2.5.2.4. Модели непротиворечивости памяти

Е

x = 1;

w = y;

ще одна весьма тонкая проблема потоков связана с моделью памяти программы. Некоторые отдельные реализации потоков исходят из определенной модели непротиворечивости (консистентности) памяти, определяющей как переменные, которые читаются и записываются разными потоками, кажутся этим потокам. Интуитивно подразумевается, что чтение переменной должно иметь своим результатом последнее, записанное в переменную значение, но что означает «последняя». Рассмотрим, например, сценарий, где все переменные инициализируются значением 0 и потокA выполняет два оператора:

y = 1;

z = x;

Тогда как поток B выполняет следующие два оператора:

Интуитивно получаем, что после выполнения потоками этих операций мы должны надеяться, что хотя бы одна из двух переменных w и z имеют значение 1. Такая гарантия относится к последовательной непротиворечивости. Это означает, что результат любого выполнения точно такой же, как если бы операции всех потоков всегда выполнялись в некотором порядке и операции каждого индивидуального потока всегда появлялись в этой последовательности в порядке, определенном в потоке.

Однако последовательная непротиворечивость не гарантируется большинством (или возможно всеми) реализаций Pthreads. Обеспечение такой гарантии весьма трудно для современных процессоров, использующих современные компиляторы. Компилятор, например, свободен в вариантах переупорядочивании команд в каждом из потоков, т.к. не существует зависимости между ними (так это может казаться компилятору). Даже если компилятор не переупорядочивает команды, это может сделать аппаратура. Поэтому для доступа к разделяемым переменным остается лишь использовать взаимоисключающие замки и надеяться, что они реализованы корректно. О проблемах непротиворечивости памяти можно посмотреть в [28], [29].

2.5.2.5. Проблемы с потоками

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

В примере на рис. 73 можно устранить потенциальную взаимоблокировку, использую простой трюк, но он приводит к коварным ошибкам (не возникают при тестировании, не извещаются при наличии). Предположим, функция update модифицирована следующим образом:

void update(int newx) {

x = newx;

// копирование списка в headc и tailc

pthread_mutex_lock(&lock);

element_t* headc = NULL;

element_t* tailc = NULL;

element_t* element = head;

while (element != 0) {

if (headc == NULL) {

headc = malloc(sizeof(element_t));

headc->listener = head->listener;

headc->next = 0;

tailc = headc;

} else {

tailc->next = malloc(sizeof(element_t));

tailc = tailc->next;

tailc->listener = element->listener;

tailc->next = 0;

}

element = element->next;

}

pthread_mutex_unlock(&lock);

// извещение пользователей с помощью копии списка

element = headc;

while (element != 0) {

(*(element->listener))(newx);

element = element->next;

}

}

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

Этот код, однако, имеет потенциально серьезную проблему, которая не может быть обнаружена тестированием. Предположим, что поток A вызывает update с аргументом newx = 0, индицирующим, что «все системы в норме». Предположим, что поток A отложен сразу после освобождения замка, но перед выполнением оповещения. Предположим, что пока он отложен поток B вызывает update с аргументом newx = 1, означающим «авария! двигатель в огне!». Предположим, что этот вызов update завершается перед тем как поток A получает шанс на выполнение. Когда поток A получает разрешение на выполнение, он будет оповещать всех слушателей, но неправильным значением! Если один из слушателей обновляет дисплей пилота самолета, дисплей будет показывать, что все нормально, когда фактически двигатель объят пламенем. Нетривиальные многопоточные программы очень трудно понимать. Они могут содержать коварные ошибки, состязания и взаимоблокировку. Проблемы многопоточных программ могут оставаться незамеченными годы даже при интенсивном использовании программ. Эти проблемы очень важны для встроенных систем т.к. оказывают влияние на безопасность и средства существования. Фактически каждая встроенная система включает параллельное программное обеспечение, потому инженеры, проектирующие встроенные системы должны противостоять всевозможным ловушкам.