82.1. ПРИМИТИВЫ СИНХРОНИЗАЦИИ



Основа - низкоуровневые операции


	Синхронизация
	 /         \
       spin        wait


spin - поток выполнений (нить) остается активным и продолжает пытаться захватить ресурс
wait - поток выполнения (нить) засыпает пока ресурс не будет освобожден


 --------------------------------------------------
          		Scalable  Fair	Sleeps
 --------------------------------------------------
spin-mutex              No        No      No
queuing-mutex		Yes	  Yes	  No
spin-semaphore	        No	  No	  No
queuing-spin-semaphore  Yes	  Yes	  No
spin-rw-lock	        No        No      No
queuing-spin-rw-lock    Yes       Yes     No
 --------------------------------------------------
mutex			OSdepend OSdepend Yes
semaphore		OSdepend OSdepend Yes
rw-lock		        OSdepend OSdepend Yes
 --------------------------------------------------



Spin-семантика


Locked операции на шине








Mutex

Mutex - это бинарный семафор. операции -------- init первоначальная инициализация переменной мьютекса trylock попытка блокировки мьютекса - возращение статуса попытки lock блокировка мьютекса (собсвенно это spin-lock) unlock освобождение уже захваченного мьютекса

Операция для захваты mutexа должна быть атомарной, иначе начинаются гонки:

Реализуется как правило нижнеуровневыми командами вида: Для x86. LOCK XCHG LOCK битовые операции (BTS, BTC) XCHG dest, src dest < --- > src BTS dest, bit_number CF = BIT(dest, bit_number) BIT(dest, bit_number) = 1 BTC dest, bit_number CF = BIT(dest, bit_number) BIT(dest, bit_number) = 0 Пример реализации: --------------------------------------------------------------------- mutex_init(&mutex, MUTEX_UNLOCKED) mov [mutex], 0 mutex_init(&mutex, MUTEX_LOCKED) mov [mutex], 1 --------------------------------------------------------------------- bool mutex_trylock(&mutex) mov eax, 1 lock xchg [mutex],eax or eax,eax mov eax, FALSE mov ebx, TRUE cmovz eax, ebx ret --------------------------------------------------------------------- mutex_unlock(&mutex) lock mov [mutex],0 --------------------------------------------------------------------- пример реализации на битовых операциях (используем бит # N) --------------------------------------------------------------------- bool mutex_trylock(&mutex) lock bts [mutex], N mov eax, FALSE mov ebx, TRUE cmovc eax, ebx ret --------------------------------------------------------------------- mutex_unlock(&mutex) lock btc [mutex], N ---------------------------------------------------------------------


Spin lock

Простейший spin-lock (собственно spin-lock) - это захват мьютекса.

Пример реализации: --------------------------------------------------------------------- spinmutex_lock(&mutex) ?spin_loop: mov eax,1 lock xchg [mutex],eax or eax,eax jz ?lock_success ;; wait jmp ?spin_loop ?lock_success: ret --------------------------------------------------------------------- Основная проблема spin-lock, это то что процессор сидящий в spin-loop переодически пытается сделать лоченную операцию к шине, что приводит к потере быстродействия системы, так как лоченные операции выполняются на порядок медленнее чем обычные. Есть команда которая ждет определенное фиксированное количество времени PAUSE Поэтому в современные микропроцессоры стали вводить команды оптимизирующие spin-locks. Например современные процессоры Intel имеют команды MONITOR и MWAIT Команда MONITOR указывает адресс который будет мониториться (в EAX), команда MWAIT осуществляет ожидание, Если происходит обращение к указанному адрессу (понятное дело из другого ядра или процессора или bus-master оборудования), то команда MWAIT заканчивает свое выполнение и управление передается следующей команды (в зависимости от параметров передаваемых в регистрах MWAIT может также прерываться а может не прерываться по прерываниям, но по NMI, SMI будет все равно прерываться). Вот так будет выглядеть spin-lock используя эти инструкции. --------------------------------------------------------------------- spinmutex_lock(&mutex) ?spin_loop: mov eax,1 lock xchg [mutex],eax or eax,eax jz ?lock_success xor ecx,ecx xor edx,edx lea eax,[mutex] monitor mwait jmp ?spin_loop ?lock_success: ret --------------------------------------------------------------------- Собственно spin-lock надо использлвать только на короткое время. В случае kernel программирования может возникнуть необходимость в IRQ safe версиях этих функций. Тогда они должны сохранить флаг прерывания, и запретить прерывания во время выполнения.


Оптимизация spin-locks С точки зрения кэширования Cache Coherence: Write-Invalidate:



Write-Update

Spin on Test&Set - Test and Set захватывает шину - падает эффективность while(Test_and_Set(&lock) == BUSY) ; // spin Spin on Read + В основном обращения идут к cache - После того как кто-то отпускает lock, все начинают долбиться по Test_and_Set, это занимает много времени, особенно если у cache политика не обновлять внешние записи, а инвалидировать while(1) { while(lock == BUSY) ; // spin - чтение из cache if (Test_and_Set(&lock) == BUSY) break; } --------------------------------------------------------------------- spinmutex_lock(&mutex) ?spin_loop: cmp [mutex],0 ; From local cache jnz ?spin_loop mov eax,1 lock xchg [mutex],eax or eax,eax jz ?lock_success jmp ?spin_loop ?lock_success: ret ---------------------------------------------------------------------


Spin-lock pthread_spin_init pthread_spin_destroy pthread_spin_lock pthread_spin_trylock pthread_spin_unlock(pthread_spinlock_t* spiner);


Атомарные операции

Как правило используются атомарные операции Atomic Increment добавляет 1 Atomic Decrement вычитает 1 Atomic Add сложение Atomic Subtract вычитание Atomic Exchange обмен Atomic Exchange Pointer Atomic Exchange Add Atomic Compare Exchange Atomic And Atomic Or Atomic Xor Типовой синтаксис: NewValue InterlockedIncrement(Address) NewValue InterlockedDecrement(Address) OldValue InterlockedExchange(Address, NewValue) OldValue InterlockedExchangeAdd(Address, Increment) InterlockedCompareExchange(Address, NewValue, Comparand) Принцип атомарных операций: RMW (Read-Modify-Write) CMPXCHG r/m,r if accumulator = dest { ZF <- 1 dest <- src } else { ZF <- 0 accumulator <- dest } CMPXCHG8B m64 if (EDX:EAX == dest) { ZF <- 1 dest <- ECX:EBX } else { ZF <- 0 EDX:EAX <- dest } Общий вид реализации атомарных операций на примере сложения: mutex_lock(&mutex); variable += value; mutex_unlock(&mutex); Но на различных архитектурах может быть оптимизирован командами, которые лочат шину. Например на x86 пример такой команды: LOCK ADD --------------------------------------------------------------------- interlocked_add32(&variable, value) lock add [variable],value --------------------------------------------------------------------- Но не всегда для всех размеров данных, например для 64-бит данных на x86 придется уже использовать mutex. --------------------------------------------------------------------- interlocked_add64(&variable, value) mutex_lock(&mutex); add [variable], VALUE_LOW_32BIT adc [variable], VALUE_HIGH_32BIT mutex_unlock(&mutex); --------------------------------------------------------------------- Зачем нужны атомарные операции? Две цели: 1) Реализация семафоров и мьютексов 2) Реализация работы с shared variables не обкладывая их мьютексами Например есть две (или более) нитей которые делают какие то вычисления паралельно и надо переодически аккумулировать результаты промежуточных вычислений от разных нитей. QNX void atomic_*(volatile unsigned* D, unsigned S) unsigned atomic_*_value(volatile unsigned* D, unsigned S) // return previous value add sub clr (*D) &= ~S set (*D) |= S toggle (*D) ^= S


Семафор

Задача семафора - позволить не более чем строго определенному числу потоков выполнения выполнять некоторый код.

В настоящее время для поддержки семафоров в некоторых современных проессорах существуют специальные команды. Например для x86 такой командой является XADD XADD dest,src temp = src + dest src = dest dest = tmp операции -------- init первоначальная инициализация переменной семафора trylock попытка блокировки семафора - возращение статуса попытки lock блокировка семафора (собсвенно это spin-semaphore) unlock освобождение уже захваченного семафора ----------------------------------------------------------------------- semaphore_init(&semaphore) mov [semaphore], 0 ----------------------------------------------------------------------- bool semaphore_trylock(&semaphore) mov eax, 1 lock xadd [semaphore], eax cmp eax, MAX_VALUE mov eax, FALSE mov ebx, TRUE cmovc eax, ebx ret ----------------------------------------------------------------------- semaphore_unlock(&semaphore) lock dec [semaphore] ----------------------------------------------------------------------- соответсвенно захват ----------------------------------------------------------------------- spinsemaphore_lock(&semaphore) ?spin_loop mov eax,1 lock xadd [semaphore],eax cmp eax, MAX_VALUE jc ?lock_success ;; wait jmp ?spin_loop ?lock_success: ret ----------------------------------------------------------------------- реализация семафора на базе mutexa: ----------------------------------------------------------------------- bool semaphore_trylock(&semaphore) spinmutex_lock(&mutex) if (*semaphore < MAX_VALUE) { (*semaphore)++; mutex_unlock(&mutex); return TRUE; } mutex_unlock(&mutex); return FALSE; ----------------------------------------------------------------------- semaphore_unlock(&semaphore) spinmutex_lock(&mutex) (*semaphore)--; mutex_unlock(&mutex) ----------------------------------------------------------------------- spinsemaphore_lock(&semaphore) while(semaphore_trylock(&semaphore) != TRUE) ; -----------------------------------------------------------------------


RW-lock

RW-lock - это дальнейшее совершенствование структуры. Есть какая структура данных, которую надо защищать. И есть два вида агентов которые к ней обращаются - читатели и писатели. Читатели могут читать паралельно друг другу (shared) Пока есть читатели писать нельзя

Писатель может быть только один (exclusive) Пока есть писатель другим нельзя ни читать не писать

Сейчас мы будем рассматривать RW-spin-lock операции читатель писатель -------------------------------------------------------- init rwlock_init trylock reader_trylock writer_trylock lock reader_lock writer_lock unlock reader_unlock writer_unlock -------------------------------------------------------- Собственно одна из идей реализации - это хранить состояние RW-lock free read_acquired write_acquired и собственно говоря счетчик читателей ----------------------------------------------------------------------- rwlock_init(&rwlock) rwlock.state = RWLOCK_FREE; rwlock.rcount = 0; mutex_init(&mutex); ----------------------------------------------------------------------- bool reader_trylock(&rwlock) mutex_lock(&mutex); switch(rwlock.state) { case RWLOCK_FREE: rwlock.state = RWLOCK_READ_ACQUIRED; // fall throught case RWLOCK_READ_ACQUIRED: rwlock.rcount++; mutex_unlock(&mutex) return TRUE; default: // RWLOCK_WRITE_ACQUIRED mutex_unlock(&mutex); return FALSE; } ----------------------------------------------------------------------- reader_unlock(&rwlock) mutex_lock(&mutex) rwlock.rcount--; if (rwlock.rcount == 0) { rwlock.state = RWLOCK_FREE; } mutex_unlock(&mutex); ----------------------------------------------------------------------- reader_lock(&rwlock) while (reader_trylock(&rwlock) != TRUE) ; ----------------------------------------------------------------------- bool writer_trylock(&rwlock) mutex_lock(&mutex) if (rwlock.state != RWLOCK_FREE) { mutex_unlock(&mutex); return FALSE; } rwlock.state = RWLOCK_WRITE_ACQUIRED; mutex_unlock(&mutex); ----------------------------------------------------------------------- writer_unlock(&rwlock) mutex_lock(&mutex); rwlock.state = RWLOCK_FREE; mutex_unlock(&mutex); ----------------------------------------------------------------------- writer_lock(&rwlock) while (writer_trylock(&rwlock) != TRUE) ; ----------------------------------------------------------------------- чем такая имплементация плоха - если постоянно и часто идут чтения то writer может и не захватить RWlock, а как правило хочеться, чтобы он его все таки захватил. решается это добавлением дополнительного состояния "write pending", при котором новым читателям не дают возможность захватить rwlock, пока не будет записи. Дальнейшее развитие - уже сбалансированные политики. Собственно для однопроцессорных машин RWlock - это некоторый аналог spin-lock (если reader или writer вошел то пока не вышел ничего хорошего). Реальное преймущество RWlock идет в многопроцессорных машинах при паралельном чтении. Иногда бывает вариант, который имеет функуию upgrade_to_writer() которая превращает reader, который уже находиться внутри lockа, во writer.


собственно кроме RWlock бывает еще и RW-semaphore. Он имеет такую же семантику, но ограничение на количество одновременных читателей. Соответсвенно изменения касаются reader_trylock: ----------------------------------------------------------------------- bool reader_trylock(&rwlock) mutex_lock(&mutex); switch(rwlock.state) { case RWLOCK_FREE: rwlock.state = RWLOCK_READ_ACQUIRED; // fall throught case RWLOCK_READ_ACQUIRED: if (rwlock.rcount >= MAX_VALUE) { mutex_unlock(&mutex); return FASLE; } rwlock.rcount++; mutex_unlock(&mutex) return TRUE; default: // RWLOCK_WRITE_ACQUIRED mutex_unlock(&mutex); return FALSE; } -----------------------------------------------------------------------


Seq-lock

Еще одно изобретение - это sequential lock. Он может использоваться когда в случаях типа RWlock, но идет защита данных про которых строго известно их распределение в памяти (не динамические цепочки), например какая то статистика итд. Данная блокировка позволяет не использовать spin-lock при чтении, ограничиваясь обычным считыванием переменной счетчика sequent лока. Если до начала операции и после ее окончания значение счетчика одинаково - значит мы считали данные в consistent состоянии Операции reader writer ----------------------------------------------------- init seqlock_init lock writer_seqlock unlock writer_sequnlock beginread reader_seqbegin endread reader_seqretry ----------------------------------------------------- Data: seqlock.counter - счетчик seqlock mutex - мьютекс для записи ----------------------------------------------------------------------- seqlock_init(&seqlock) seqlock.counter = 0; init_mutex(&mutex) ----------------------------------------------------------------------- writer_seqlock(&seqlock) mutex_lock(&mutex) seqlock.counter++; ----------------------------------------------------------------------- writer_sequnlock(&seqlock) seqlock.counter-- mutex_unlock(&mutex) ----------------------------------------------------------------------- int reader_seqbegin(&seqlock) return atomic_read(seqlock.counter) ----------------------------------------------------------------------- bool reader_seqretry(&seqlock, int prev) if (atomic_read(seqlock.counter) != prev) { return TRUE; } return FALSE; -----------------------------------------------------------------------

В результате семантика writera имеет вид: writer_seqlock(&seqlock); // WRITE actions writer_sequnlock(&seqlock); а семантика читателя: int seq; do { seq = reader_seqbegin(&seqlock); // READ actions } while( reader_seqretry(&seqlock, seq));


в общем виде spin операции не гарантируют доступ к ресурсу (может получиться так, что тот кто пришел позднее получил ресурс раньше). Что бы с этим бороться можно делать более сложные структуры которые будут содержать очереди. structures w/o pointers a-la statistics +-----------------+ | data | +-----------------+ | data | +-----------------+


Seq-lock cинхронизация и Out-Of-Order Execution Out-of-order execution: need barrier barrier is locked access LOCK xxx or special commands MFENCE SFENCE LFENCE на примере SeqLock прочитали LockCounter 1й раз прочитали данные прочитали LockCounter 2й раз поскольку Out-of-order execution то возможна ситуация типа прочитали LockCounter 1й раз прочитали LockCounter 2й раз прочитали данные что в общем то не то что мы хотели получить. что бы ее избежать делаем memory barrier по чтению прочитали LockCounter 1й раз прочитали данные Read Memory Barrier прочитали LockCounter 2й раз На самом деле их 2 типа Барьеры оптимизации (оптимизирующий компилятор переставляет) Барьеры памяти (процессор выполняет не в порядке)


wait-семантика

Основа wait-семантики: если мы не можем прямо сейчас захватить объект, система погружает нашу нить в сон пока объект не будет освобожден. Cуществует различная семантика пробуждения при освобождении объекта: просыпается одна нить просыпается указанное число нитей просыпаются все нити которые ждали объект Есть несколько функций ожидания ожидать один объект ожидать группу объектов хотя бы один из них все из них сразу Ожидание может быть лимитированно по времени, а может быть и нелимитированно. Как правило объекты для ожидания являются именованными. Например в Windows есть: WaitForSingleObject() WaitForMultipleObjects() Может и не дождаться - если система обнаружит dead lock WAIT_OBJECT_0 WAIT_ABANDONED WAIT_TIMEOUT WAIT_FAILED Waitable Objects Как правило бывают именованные и неименованные. Неименованные - это как правило просто адресс памяти или HANDLE который используется для общения внутри одного процесса или иерархии родитель-дочерние процессы. Именованные используются для работы между совершенно различными процессами. Как выглядит Waitable Object Во первых это spin-mutex который его защищает Во вторых владелец, в третих список тех кто ждет +------------+ | Mutex | +------------+ +--------------+ | Owner |------------>|Process/Thread| +------------+ +--------------+ | Waiters | +------------+ Waiters могут быть построены в виде очереди (так удобнее отслеживать кого отпускать первого если идет pulse) Как правило в системах есть определенное ограничение не ресурсы из серии - объект могут ждать не более N processed/thread. Бывают случаи когда при создании объектов можно указать максимальное количество ожидающих. Во вторых структуры process/thread имеют указатели на объекты которыми они владеют и на объекты которые они ждут. Это позволяет во первых отслеживать deadlocks (см следущую секцию), а во вторых в случае завершения нити/процесса - позволяет отпускать залоченные ими ресурсы, а так же удалять из списка ждущих. +-------------+ own +----------+ +---------+ | Process |------>| Object |---->| Object | | -------- | +----------+ +---------+ | Thread | +-------------+ | wait +----------+ +---------+ +------------>| Object |---->| Object | +----------+ +---------+ Классы waitable objects: - Чисто Объекты синхронизации (семафоры, мьютесксы итд) работа с ними происходит в явном виде - События по таймеру Как только таймер истекает объект устанавливается - Завершения (процессы, потоки, jobs, асинхронные операции ввода/вывода) - Ожидание ввода (каналы, сокеты, cообщения, ввод с консоли) - Ожидание изменений (например Win32 ожидание изменений для HANDLE полученного с помощью FindFirstChangeNotification())


Mutex



операции Win32 UNIX --------------------------------------------------------------- init CreateMutex() pthread_mutex_init() open OpenMutex() delete CloseHandle() pthread_mutex_destroy() trylock pthread_mutex_trylock() lock WaitForSingleObject() pthread_mutex_lock() unlock ReleaseMutex() pthread_mutex_unlock() --------------------------------------------------------------- ------------------------------------------------------------------- HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName); ------------------------------------------------------------------- HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); ------------------------------------------------------------------- BOOL ReleaseMutex(HANDLE hMutex); ------------------------------------------------------------------- DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds); ------------------------------------------------------------------- Пример использования: HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); if (hMutex == NULL) { // process errors } DWORD dwWriteResult = WaitForSingleObject(hMutex, 5000L); switch(dwWriteResult) { case WAIT_OBJECT_0: // here is code, which is protected by this mutex if (!ReleaseMutex(hMutex)) { // process errors } break; case WAIT_TIMEOUT: case WAIT_ABANDONED: // we not accqure mutex } CloseHandle(hMutex); ------------------------------------------------------------------- int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); ------------------------------------------------------------------- int pthread_mutex_destroy(pthread_mutex_t *mutex); ------------------------------------------------------------------- int pthread_mutex_trylock(pthread_mutex_t *mutex); ------------------------------------------------------------------- int pthread_mutex_lock(pthread_mutex_t *mutex); ------------------------------------------------------------------- int pthread_mutex_unlock(pthread_mutex_t *mutex); ------------------------------------------------------------------- int pthread_mutexattr_init int pthread_mutexattr_destroy(pthread_mutexattr_t* attr); pthread_mutexattr_set get prioceiling // priority of mutex owner protocol pshared // process shared recursive type protocol PTHREAD_PRIO_INHERIT PTHREAD_PRIO_PROTECT pshared PTHREAD_PROCESS_SHARED PTHREAD_PROCESS_PRIVATE recursive PTHREAD_RECURSIVE_ENABLE PTHREAD_RECURSIVE_DISABLE type PTHREAD_MUTEX_NORMAL PTHREAD_MUTEX_ERRORCHECK PTHREAD_MUTEX_RECURSIVE int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr); int pthread_mutex_lock(pthread_mutex_t* mutex); int pthread_mutex_trylock(pthread_mutex_t* mutex); int pthread_mutex_timedlock(pthread_mutex_t* mutex, const struct timespec* abstimeout); // QNX int pthread_mutex_unlock(pthread_mutex_t* mutex); int pthread_mutex_destroy(pthread_mutex_t* mutex);


Semaphore



операции Win32 -------------------------------- init CreateSemaphore() open OpenSemaphore() delete CloseHandle() lock WaitForSingleObject() unlock ReleaseSemaphore() --------------------------------- ------------------------------------------------------------------- HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName); ------------------------------------------------------------------- HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); ------------------------------------------------------------------- BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount); ------------------------------------------------------------------- Семантика использования: Wait condition: // THREAD 1 // THREAD 2 while(!expr) expr = true; { post(&sem); wait(&sem) } Exclusive actions: // THREAD 1 // THREAD 2 wait(&sem); wait(&sem); // critical section // critical section post(&sem); post(&sem); Count semaphore: // THREAD 1 // THREAD 2 while(true) while(true) { { // action wait(&sem); post(&sem); // action } } UNIX/QNX semaphore.h int sem_init(sem_t* sem, int shared, unsigend int value); // Unnamed int sem_destroy(sem_t* sem); sem_open // Named (open or create) sem_close int sem_wait(sem_t* sem); int sem_trywait(sem_t* sem); int sem_timedwait(sem_t* sem, const timespec* abstimeout); int sem_post(sem_t* sem); int sem_getvalue(sem_t* sem, int* value); int sem_unlink(const char* name)


RWLock

int pthread_rwlock_destroy(pthread_rwlock_t *); int pthread_rwlock_init(pthread_rwlock_t *, const pthread_rwlockattr_t *); int pthread_rwlock_rdlock(pthread_rwlock_t *); int pthread_rwlock_tryrdlock(pthread_rwlock_t *); int pthread_rwlock_trywrlock(pthread_rwlock_t *); int pthread_rwlock_unlock(pthread_rwlock_t *); int pthread_rwlock_wrlock(pthread_rwlock_t *); int pthread_rwlock_timedrdlock(pthread_rwlock_t* rwl, const struct timespec* abs) pthread_rwlock_timedwrlock При этом помним про upgrade: reader to writer, и downgrade: writer to reader.


Event





операции Win32 -------------------------------- init CreateEvent() open OpenEvent() delete CloseHandle() signaled SetEvent() PulseEvent() nonsignaled ResetEvent() wait WaitForSingleObject() -------------------------------- ------------------------------------------------------------------- HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName); ------------------------------------------------------------------- HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); ------------------------------------------------------------------- BOOL PulseEvent(HANDLE hEvent); ------------------------------------------------------------------- BOOL SetEvent(HANDLE hEvent); ------------------------------------------------------------------- BOOL ResetEvent(HANDLE hEvent); -------------------------------------------------------------------


Conditional vars

------------------------------------------------------------------- int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *); ------------------------------------------------------------------- int pthread_cond_destroy(pthread_cond_t *); ------------------------------------------------------------------- int pthread_cond_signal(pthread_cond_t *); ------------------------------------------------------------------- int pthread_cond_broadcast(pthread_cond_t *); ------------------------------------------------------------------- int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *); ------------------------------------------------------------------- int pthread_cond_timedwait(pthread_cond_t *, pthread_mutex_t *, const struct timespec *); ------------------------------------------------------------------- Condition variables int pthread_condattr_init(pthread_condattr_t* attr) int pthread_condattr_destroy(pthread_condattr_t* attr); int pthread_condattr_set get pshared clock // select timer int pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* attr); int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex); // block on cond, unlock mutex int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespsec* abstime); // QNX int pthread_cond_signal(pthread_cond_t* conde) // one thread, then thread try to lock it mutex int pthread_cond_broadcast(pthread_cond_t* cond); // all int pthread_cond_destroy(pthread_cond_t* cond);


Критические секции



Критическая секция - кусок кода который будет выполняться без вытеснения

Собственно критическая секция: waitable_mutex_lock(&mutex); // Here is critical section itself waitable_mutex_unlock(&mutex); ------------------------------------------------------------------- void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ------------------------------------------------------------------- BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ------------------------------------------------------------------- void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ------------------------------------------------------------------- void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); ------------------------------------------------------------------- void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); -------------------------------------------------------------------


барьеры



LockFree barrier: if (InterlockedIncrement(&ActiveThreads) == MaxThreads) { SetEvent(StartEvent); } else { WaitForSingleObject(StartEvent, INFINIFY); }


Реентерабильные семафоры и мьютексы

Реентерабильные - позволяют делать многократный захват из одной и той же нити. Как делать реентерабельные мьютексы из нереентерабильных? Нужна структура которая содержит информацию о реентерабильном мьютексе, а также простой мьютекс которым защищена эта структура: +----------------+ | Thread ID | +----------------+ | Lock Count | +----------------+ | Mutex | +----------------+ bool trylock_reenterable_mutex(reenter_mutex remutex) { if (trylock_mutex(remutex.mutex) == false) { return false; } if (remutex.lockcount != 0) { if (remutex.threadid == CurrentThreadId()) { remutex.lockcount++; unlock_mutex(remutex.mutex) return true; } else { unlock_mutex(remutex.mutex) return false; // Busy by other thread } } else { remutex.threaid = CurrentThreadId(); remutex.lockcount++; unlock_mutex(remutex.mutex); return true; } } bool unlock_reenterable_mutex(reenter_mutex remutex) { lock_mutex(remutex.mutex); if (remutex.lockcount == 0) { unlock_mutex(remutex.mutex); return false; // Mutex already free } if (remutex.threadid != CurrentThreadId()) { unlock_mutex(remutex.mutex); return false; // Try to unlock in wrong thread } remutex.lockcount--; unlock_mutex(remutex.mutex); return true; // Success }


Queued-mutex

Основная идея: в обычном мьютексе все долбяться чтобы получить доступ, а мьютекс занят достаточно долго. В queued-mutex долбяться что бы получить доступ к очереди, а это гораздо быстрее. que[MAX] +--------+ +-------+ | amutex | | que[0]| +--------+ +-------+ | current| | que[1]| +--------+ +-------+ | next | | ... | +--------+ +-------+ | que[N]| +-------+ bool lock_queued_mutex(queued_mutex mutex) { lock_mutex(amutex); if (next == current) return ERROR_CAPACITY_OF_QUEUED_MUTEX_EXCEEDED; address = &que[next]; next++; if (next >= MAX) next = 0; unlock_mutex(amutex); while(*address == UNLOCKED) ; // читает только из cache return SUCCESS; } bool unlock_queued_mutex(queued_mutex mutex) { lock_mutex(amutex); que[current] = UNLOCKED; current++; if (current >= MAX) current = 0; que[current] = LOCKED; unlock_mutex(amutex); }

Index Prev Next