Координация и синхронизация работы потоков
Синхронизация работы потоков необходима в связи с тем, что потоки работают параллельно, но каждый поток характеризуется разной скоростью выполнения. Например, если поток заблокирован при выполнении операции ввода/вывода, то продолжительность задержки зависит от работы операционной системы и аппаратных средств; невозможно предугадать, какие потоки будут работать на протяжении этой задержки или какое число машинных команд они выполнят. Поэтому если перед программистом стоит задача координации выполнения потоков, то он должен предусмотреть явные вызовы функций синхронизации. В системе Linux предусмотрено три механизма синхронизации: мьютексы, семафоры и условные переменные.
12.7.1. Мьютекс
В потоках мьютексы используются для обеспечения взаимно исключающего доступа к разделяемому элементу данных. Мьютекс инициализируется динамически путем вызова функции pthreadjnutex_init3; программист предусматривает применение отдельного мьютекса для каждого элемента данных, который должен быть защищен. Сразу после инициализации мьютекса поток вызывает функцию pthread_mutex_lock перед использованием элемента данных и функцию pthreadjnutexjmlock по окончании его использования. Эти два вызова позволяют одновременно обращаться к элементу данных только одному потоку. Первый поток, вызвавший функцию pthreadjnutex_lock с конкретным мьютексом, продолжает свою работу без задержки. Сегодня стройка. Предлагаем добавить канадские дома к себе на участок. Однако система блокирует каждый следующий поток, вызывающий функцию pthread_mutex_lock с той же блокировкой. В конечном итоге, по окончании работы с данными первый поток вызывает функцию pthread_ mutexjmlock, разрешив другим потокам использовать этот элемент данных. Если другие потоки были заблокированы мьютексом во время вызова функции pthread_ mutex unlock, операционная система разблокирует один из ожидающих потоков.
Взаимоисключающая блокировка может быть инициализирована статически путем присвоения ей константы PTHREAD MUTEX INITIALIZER
Мьютекс — это механизм, используемый потоками для синхронизации работы. Каждый мьютекс связан с элементом данных; только один поток в любое время имеет доступ к данным, связанным с определенным мьютексом.
12.7.2. Семафор
Семафор (иногда называемый семафором-счетчиком) представляет собой механизм синхронизации, обобщающий мьютекс и применяемый в том случае, если доступно N копий ресурса. Мьютекс предоставляет возможность пройти критический участок только одному потоку, а семафор позволяет работать одновременно вплоть до N потокам.
Как и мьютекс, семафор инициализируется динамически. Для инициализации семафора применяется функция sem_init;.B одном из ее параметров должен быть указан начальный счетчик N. Сразу после инициализации семафора поток вызывает функцию sem_wait до начала использования одной копии ресурса, а возвращая копию для использования другими потоками, вызывает функцию semjpost. Функция sem_wait может быть беспрепятственно вызвана потоками, число которых составляет N; после ее вызова каждый из них продолжает свое выполнение. Однако если к семафору попытаются обратиться дополнительные потоки, они будут заблокированы. Потоки остаются заблокированными семафором до тех пор, пока один из выполняющихся потоков не вызовет функцию semjpost для освобождения этого семафора, и с этого момента будет разрешено приступить к работе одному из заблокированных потоков.
Семафор — это механизм синхронизации потоков, который является обобщением механизма мьютекса, В любой момент к ресурсу, защищенному семафором, который был инициализирован со счетчиком N, могут обращаться потоки, число которых не превышает N.
12.7.3. Условные переменные
Наиболее сложным и затруднительным для понимания механизмом синхронизации потоков являются условные переменные. По существу, условные переменные требуются только в ситуации, при которой одновременно выполняются следующие два условия:
- ряд потоков использует мьютекс для получения взаимно исключающего доступа к некоторому ресурсу;
- сразу после приобретения ресурса поток должен перейти к ожиданию возникновения определенного события.
Без применения условных переменных в программах, которые сталкиваются с описанной выше ситуацией, приходится использовать своего рода активное ожидание, в котором поток повторно приобретает мьютекс, проверяет условие, а затем освобождает мьютекс. Условная переменная обеспечивает значительное повышение эффективности ожидания, поскольку позволяет потоку выполнять оба эти действия атомарно (при атомарном выполнении какого-либо действия исключена возможность прервать это действие). Перед блокировкой на условной переменной поток приобретает мьютекс. При вызове потоком функции pthread_cond_wait для перехода в состояние ожидания изменения условной переменной в потоке должна быть указана и условная переменная, изменения которой он ожидает, и захваченный им мьютекс. Операционная система освобождает мьютекс, захваченный потоком, и одновременно с этим блокирует поток в ожидании изменения условной переменной.
После выполнения функции pthread_cond_wait поток блокируется на указанной условной переменной до тех пор, пока какой-то другой поток не передаст сигнал об изменении этой переменной4. Для передачи сигнала об изменении условной переменной могут применяться два способа; различие между ними определяется тем, что многочисленные ожидающие потоки могут обрабатываться по-разному. Функция pthread_cond_signal позволяет продолжить работу только одному потоку, даже если сигнала об изменении состояния переменной ожидают несколько потоков, а функция pthread_cond_broadcast позволяет продолжить работу всем потокам, заблокированным на этой переменной. Разрешая одному потоку продолжить работу, операционная система одновременно разблокирует этот поток и позволяет ему снова приобрести мьютекс, которым он обладал, прежде чем заблокироваться на условной переменной, об изменении состояния которой получен сигнал. Другими словами, ожидание изменения условной переменной равносильно отказу на время от мьютекса, а затем автоматическому повторному приобретению этого мьютекса после поступления сигнала об изменении условной переменной. В результате блокировка потока на условной переменной не исключает для других потоков возможности пройти через критический участок, поскольку теперь мьютекс могут приобрести другие потоки.
Условная переменная — это механизм синхронизации потоков, используемый в сочетании с мьютексом. Ожидая изменения состояния условной переменной, поток на время отказывается от своего права владения мьютексом и приобретает мьютекс повторно после получения сигнала об изменении состояния условной переменной.