创建复杂的同步对象

线程库中提供的子例程能够用作原语来构建更复杂的同步对象。

长锁

线程库提供的互斥对象是低争用对象,并且不应该被保留很长时间。 长锁是通过互斥对象和条件变量实现的,所以长锁能被保留很长时间而不影响程序性能。 如果启用了可取消,就不应该使用长锁。

长锁的数据类型为 long_lock_t。 它必须通过 long_lock_init 例程初始化。 long_locklong_trylocklong_unlock 子例程执行与 pthread_mutex_lock类似的操作。 pthread_mutex_trylockpthread_mutex_unlock 子例程。

下面的示例说明了条件变量的典型使用。 在此示例中,不检查锁所有者。 结果是,任何线程可以解锁任何锁。 不会执行错误处理和取消处理。
typedef struct {
        pthread_mutex_t lock;
        pthread_cond_t cond;
        int free;
        int wanted;
} long_lock_t;

void long_lock_init(long_lock_t *ll)
{
        pthread_mutex_init(&ll->lock, NULL);
        pthread_cond_init(&ll->cond);
        ll->free = 1;
        ll->wanted = 0;
}

void long_lock_destroy(long_lock_t *ll)
{
        pthread_mutex_destroy(&ll->lock);
        pthread_cond_destroy(&ll->cond);
}

void long_lock(long_lock_t *ll)
{
        pthread_mutex_lock(&ll->lock);
        ll->wanted++;
        while(!ll->free)
                pthread_cond_wait(&ll->cond);
        ll->wanted--;
        ll->free = 0;
        pthread_mutex_unlock(&ll->lock);
}

int long_trylock(long_lock_t *ll)
{
        int got_the_lock;

        pthread_mutex_lock(&ll->lock);
        got_the_lock = ll->free;
        if (got_the_lock)
                ll->free = 0;
        pthread_mutex_unlock(&ll->lock);
        return got_the_lock;
}

void long_unlock(long_lock_t *ll)
{
        pthread_mutex_lock(&ll->lock);
        ll->free = 1;
        if (ll->wanted)
                pthread_cond_signal(&ll->cond);
        pthread_mutex_unlock(&ll->lock);
}

信号量

UNIX系统中的传统 semaphores 是进程间同步设施。 对于特定用法,您能够实现线程间信号。

信号量具有 sema_t 数据类型。 它必须由 sema_init 例程初始化,并使用 sema_destroy 例程销毁。 信号 wait 和信号 post 操作分别由 sema_psema_v 例程执行。

在下面的基本实现中,不执行错误处理,但是在需要时使用清除处理程序适当地处理取消:
typedef struct {
        pthread_mutex_t lock;
        pthread_cond_t cond;
        int count;
} sema_t;

void sema_init(sema_t *sem)
{
        pthread_mutex_init(&sem->lock, NULL);
        pthread_cond_init(&sem->cond, NULL);
        sem->count = 1;
}

void sema_destroy(sema_t *sem)
{
        pthread_mutex_destroy(&sem->lock);
        pthread_cond_destroy(&sem->cond);
}

void p_operation_cleanup(void *arg)
{
        sema_t *sem;

        sem = (sema_t *)arg;
        pthread_mutex_unlock(&sem->lock);
}

void sema_p(sema_t *sem)
{
        pthread_mutex_lock(&sem->lock);
        pthread_cleanup_push(p_operation_cleanup, sem);
        while (sem->count <= 0)
                pthread_cond_wait(&sem->cond, &sem->lock);
        sem->count--;
        /*
         *  Note that the pthread_cleanup_pop subroutine will
         *  execute the p_operation_cleanup routine
         */
        pthread_cleanup_pop(1);
}

void sema_v(sema_t *sem)
{
				pthread_mutex_lock(&sem->lock);
				if (sem->count <=0)
				 	 	pthread_cond_signal(&sem->cond);
				sem->count++;
				pthread_mutex_unlock(&sem->lock);
}

计数器指定允许使用信号的用户数。 它并不一定必须为负值;因此它不指定等待用户数(如传统信号)。 此实现为 pthread_cond_wait 子例程上的多个唤醒问题提供典型的解决方案。 信号等待操作是可取消的,因为 pthread_cond_wait 子例程提供了一个取消点。

写优先的读/写锁

写优先的读/写锁为多个线程提供对受保护资源的同时只读访问,并为单个线程提供对资源的写访问,同时排除读访问。 当写操作释放锁时,其他等待的写程序将在任何等待的读程序之前获取锁。 写优先的读/写锁通常用来保护读比写频繁得多的资源。

写优先的读/写锁的数据类型为 rwlock_t。 它必须通过 rwlock_init 例程初始化。 rwlock_lock_read 例程锁定读程序的锁(允许多个读程序),rwlock_unlock_read 例程为其解锁。 rwlock_lock_write 例程锁定写程序的锁,rwlock_unlock_write 例程为其解锁。 必须调用合适的解锁例程(用于读程序或者写程序的解锁例程)。

在下面的示例中,不检查锁所有者。 结果是,任何线程可以解锁任何锁。 例程 (例如 pthread_mutex_trylock 子例程) 缺失,并且未执行错误处理,但在需要时使用清除处理程序正确处理取消。
typedef struct {
        pthread_mutex_t lock;
        pthread_cond_t rcond;
        pthread_cond_t wcond;
        int lock_count;  /* < 0 .. held by writer             */
                         /* > 0 .. held by lock_count readers */
                         /* = 0 .. held by nobody             */
        int waiting_writers;  /* count of wating writers      */
} rwlock_t;

void rwlock_init(rwlock_t *rwl)
{
        pthread_mutex_init(&rwl->lock, NULL);
        pthread_cond_init(&rwl->wcond, NULL);
        pthread_cond_init(&rwl->rcond, NULL);
        rwl->lock_count = 0;
        rwl->waiting_writers = 0;
}

void waiting_reader_cleanup(void *arg)
{
        rwlock_t *rwl;

        rwl = (rwlock_t *)arg;
        pthread_mutex_unlock(&rwl->lock);
}

void rwlock_lock_read(rwlock_t *rwl)
{
        pthread_mutex_lock(&rwl->lock);
        pthread_cleanup_push(waiting_reader_cleanup, rwl);
        while ((rwl->lock_count < 0) && (rwl->waiting_writers))
                pthread_cond_wait(&rwl->rcond, &rwl->lock);
        rwl->lock_count++;
        /*
         *  Note that the pthread_cleanup_pop subroutine will
         *  execute the waiting_reader_cleanup routine
         */
        pthread_cleanup_pop(1);
}

void rwlock_unlock_read(rwlock_t *rwl)
{
        pthread_mutex_lock(&rwl->lock);
        rwl->lock_count--;
        if (!rwl->lock_count)
                pthread_cond_signal(&rwl->wcond);
        pthread_mutex_unlock(&rwl->lock);
}

void waiting_writer_cleanup(void *arg)
{
        rwlock_t *rwl;

        rwl = (rwlock_t *)arg;
        rwl->waiting_writers--;
        if ((!rwl->waiting_writers) && (rwl->lock_count >= 0))
                /*
                         * This only happens if we have been canceled
                         */
                        pthread_cond_broadcast(&rwl->wcond);
                        pthread_mutex_unlock(&rwl->lock);
}

void rwlock_lock_write(rwlock_t *rwl)
{
        pthread_mutex_lock(&rwl->lock);
        rwl->waiting_writers++;
        pthread_cleanup_push(waiting_writer_cleanup, rwl);
        while (rwl->lock_count)
                pthread_cond_wait(&rwl->wcond, &rwl->lock);
        rwl->lock_count = -1;
        /*
         *  Note that the pthread_cleanup_pop subroutine will
         *  execute the waiting_writer_cleanup routine
         */
        pthread_cleanup_pop(1);
}

void rwlock_unlock_write(rwlock_t *rwl)
{
        pthread_mutex_lock(&rwl->lock);
        l->lock_count = 0;
        if (!rwl->wating_writers)
                pthread_cond_broadcast(&rwl->rcond);
        else
                pthread_cond_signal(&rwl->wcond);
        pthread_mutex_unlock(&rwl->lock);
}

仅对阅读器计数。 当计数到达零时,正在等待的写程序可能获得锁。 只有一个写程序能够持有锁。 当锁被写程序释放后,如果有另一个写程序,那么将其唤醒。 否则,所有正在等待的阅读器被唤醒。

锁定例程是可取消的,因为它们调用 pthread_cond_wait 子例程。 因此,清除处理程序在调用子例程之前被注册。