`

条件变量、自旋锁和屏障

阅读更多
    条件变量本身是由互斥量保护的,线程在改变条件状态之前必须首先锁住互斥量。
    下面一组函数可用来操作条件变量。
#include <pthread.h>
int pthread_cond_init( pthread_cond_t *restrict cond,
                       const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait( pthread_cond_t *restrict cond,
                            pthread_mutex_t *restrict mutex,
                            const struct timespec *restirct tsptr);

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
                       /* 所有函数的返回值:若成功,返回 0;否则,返回错误编号 */

    pthread_cond_init 和 pthread_cond_destroy 函数分别用来初始化和销毁条件变量。参数 attr 可用来设置条件变量的属性,为 NULL 时表示使用默认属性。对于静态分配的条件变量,可以使用 PTHREAD_COND_INITIALIZER 常量来进行初始化。
    pthread_cond_wait 会等待条件变量变为真。传递给 pthread_cond_wait 的互斥量可对条件进行保护。调用者把锁住的互斥量传给函数,然后函数会自动把调用线程放到等待条件的线程列表上,并对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。该函数返回时,互斥量会再次被锁住。pthread_cond_timedwait 函数同 pthread_cond_wait,不过提供了定时功能。如果到达指定的时刻条件还没出现,该函数将重新获取互斥量,然后返回错误 ETIMEDOUT。注意这两个函数在成功返回时,线程需要重新计算条件,因为另一个线程可能在运行并改变了条件。
    pthread_cond_signal 和 pthread_cond_broadcast 可用来通知线程条件已经满足,前者至少能唤醒一个等待该条件的线程,而后者则能唤醒所有等待该条件的线程。
    下面这个程序片段演示了如何结合条件变量和互斥量来同步线程。
#include <pthread.h>

struct msg{
	struct msg	*m_next;
	/* more stuff here */
};

struct msg	*workq;

pthread_cond_t	qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t	lock = PTHREAD_MUTEX_INITIALIZER;

void process_msq(void){
	for(;;){
		pthread_mutex_lock(&lock);
		while(workq == NULL)
			pthread_cond_wait(&qready, &lock);
		struct msg *mp = workq;
		workq = workq->m_next;
		pthread_mutex_unlock(&lock);
		/* now process mp */
	}
}

void enqueue_msg(struct msg *mp){
	pthread_mutex_lock(&lock);
	mp->m_next = workq;
	workq = mp;
	pthread_mutex_unlock(&lock);
	pthread_cond_signal(&qready);
}


    自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁前一直处于忙等待状态。因此它一般用于这种情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。
    自旋锁也有一组类似于互斥量的函数。
#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
                      /* 所有函数的返回值:若成功,返回 0;否则,返回错误编号 */

    pthread_spin_init 对自旋锁进行初始化,pthread_spin_destroy 则对其进行反初始化。只有一个属性是自旋锁持有的,该属性只在支持线程进程共享同步选项的平台上才用得到。pshared 参数表示进程共享属性,表明自旋锁是如何获取的。如果它为 PTHREAD_PROCESS_SHARED,则自旋锁能被可以访问锁底层内存的线程所获取,即使它们属于不同的进程;而如果它为 PTHREAD_PROCESS_PRIVATE,则自旋锁就只能被初始化该锁的进程内部的线程所访问。
    pthread_spin_lock 和 pthread_spin_trylock 可对自旋锁加锁。前者在获取锁前一直阻塞,后者如果不能获取锁就立即返回 EBUSY 错误而不会自旋。两种方式加的锁都可以使用 pthread_spin_unlock 进行解锁。

    屏障是用户协调多个用户并行工作的同步机制,它允许每个线程等待,直到所有的合作线程都达到某一点后再继续运行。pthread_join 函数就是一种屏障的例子。
    屏障也有一组操作函数。
#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
                 const pthread_barrierattr_t *restrict attr, unsigend int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
                      /* 两个函数的返回值:若成功,返回 0;否则,返回错误编号 */
int pthread_barrier_wait(pthread_barrier_t *barrier);
    /* 返回值:若成功,返回 0 或者 PTHREAD_BARRIER_SERIAL_THREAD;否则,返回错误编号*/

    使用 pthread_barrier_init 初始化屏障时,可以用 count 参数指定在允许所有线程继续运行之前,必须到达屏障的线程数目。attr 参数是屏障属性,为 NULL 时表示使用默认属性。pthread_barrier_destroy 函数则用来释放分配给屏障的资源。
    pthread_barrier_wait 函数可用来等待其他线程赶上来。调用该函数的线程在屏障计数未满足条件时会进入休眠状态。如果该线程是最后一个调用该函数的线程,就满足了屏障计数,所有的线程都被唤醒。对于任意一个线程,该函数返回了 PTHREAD_BARRIER_SERIAL_THREAD,其他线程看到的返回值是 0。这使得一个线程可以作为主线程工作在其他所有线程已完成的工作结果上。一旦达到屏障计数,而且线程处于非阻塞状态,屏障就可以被重用。但除非在调用了 pthread_barrier_destroy 后又调用 pthread_barrier_init 修改了计数,否则屏障计数不会改变。
分享到:
评论

相关推荐

    nachos Lab3实习报告.pdf

    实践部分则要求学生基于信号量实现锁、条件变量,并解决生产者消费者问题、读者写者问题,以及实现更高级的同步机制,如屏障(barrier)和读/写锁(read/writelock)。 在任务完成情况部分,学生列举了具体的练习...

    linux内核知识系列:同步与互斥

    此外,还有其他同步机制如条件变量(condition variable)和内存屏障(memory barrier),它们在特定场景下也有重要作用。内存屏障确保指令的执行顺序,防止编译器和处理器优化导致的数据不一致。 在实际应用中,...

    Linux IO 之 IO与网络模型.pdf

    信号量在获取和释放时,通过自旋锁保护,以避免多个线程同时访问同一个资源。 四、互斥锁(Mutex) 互斥锁是一种锁机制,它可以控制同一时刻只有一个线程进入临界区,让无法进入临界区的线程休眠。互斥锁可以保护...

    iOS线程同步方案

    在iOS中,虽然没有直接提供读写锁的API,但可以通过组合使用互斥锁和条件变量(如`NSCondition`)来模拟实现。这样可以在多读少写的情况下提高效率。 5. ** gcd 的同步队列(Serial Queue)**: GCD(Grand ...

    深入理解linux内核 第三版 中文版 高清 pdf 第五章

    9. **内核同步工具**:除了上述机制,Linux内核还提供了其他同步工具,如顺序锁、完成变量、屏障指令等,这些工具在特定场景下有其独特优势,书中会有相应的介绍和示例。 总之,《深入理解Linux内核》第三版第五章...

    Linux操作系统课程指导:Ch9-10 Kernel Synchronization.ppt

    5. 完成变量(Completion Variables)、顺序锁、抢占禁用和排序与屏障(Ordering & Barriers):这些是更高级的同步工具,用于确保操作按特定顺序执行,或者确保在特定点所有处理器都能看到一致的内存视图。...

    LINUX内核内存屏障

    - 在处理共享数据结构时,如自旋锁、信号量等,内存屏障可以确保数据的一致性。 - 在实现原子操作时,内存屏障可以防止指令重排序导致的问题。 - 在使用缓存一致性模型的系统中,内存屏障可以帮助维护缓存一致性。 ...

    Java并发体系.pdf

    volatile通过内存屏障和特定的内存语义保证了线程间的数据同步,避免了缓存不一致的问题。 在实际应用中,如DCL(Double Check Locking)单例模式,我们需要关注重排序可能导致的问题。为了解决这个问题,我们可以...

    linux之线程同步一-.zip

    在Linux操作系统中,线程同步是多线程编程中的一个重要概念,它确保了多个线程在访问共享资源时能够有序、正确地执行,避免数据不一致性和竞态条件等问题。本资料包“linux之线程同步一”主要探讨了Linux环境下的...

    深入理解linux内核中文第三版-第五章

    6. **条件变量(Condition Variables)**:这是一种等待和唤醒机制,允许线程在满足特定条件时挂起,条件满足后再被唤醒继续执行。结合信号量或自旋锁,可以实现更复杂的同步策略。 7. **原子操作(Atomic ...

    Linux与unix shell编程指南

    在SMP系统中,自旋锁首先禁用内核抢占,然后检查锁的状态。如果锁已被占用,则释放内核抢占并等待,直到锁可用为止;如果锁为空闲状态,则直接获取锁。 在单处理器系统中,自旋锁主要用来禁止内核抢占,以确保临界...

    atomic_ops.pdf

    它能够确保在屏障前的操作先于屏障后的操作完成,这对于保持数据的一致性和防止竞态条件至关重要。 总结来说,Linux系统中的原子操作提供了强大而灵活的工具,用于实现多线程程序中的同步需求。通过严格遵守文档中...

    STM32MP135实现原子变量驱动【支持STM32MP1系列单片机_Linux驱动】.zip

    5. **锁机制**:虽然原子变量可以避免数据竞争,但在某些情况下,可能还需要更高级别的互斥锁(mutex)或自旋锁(spinlock)来保护更复杂的共享资源。不过,原子变量在轻量级的同步需求上更为高效。 6. **驱动注册...

    Java并发体系1

    它通过内存屏障保证了写入volatile变量后立即刷新到主内存,以及读取时直接从主内存读取,从而避免了重排序问题。 7. **双重检查锁定(DCL)模式**: DCL是一种创建线程安全单例的常用方法,它结合了volatile和...

    STM32MP157实现原子变量【支持STM32MP1系列单片机_Linux驱动】.zip

    5. **内核同步机制**:除了原子变量,还可能涉及到信号量、自旋锁、读写锁等同步机制,它们与原子变量结合使用,可以构建更复杂的并发控制策略。 6. **驱动模型**:理解Linux设备驱动模型,如总线、设备、驱动之间...

    Linux内核源码深度解析与开发实战视频.zip

    16:不可睡眠锁:自旋锁spinlock编码示例_rec 17:不可睡眠锁:RCUread-copy-update_rec 18:可睡眠锁-互斥量mutex_rec 19:可睡眠锁:信号量semaphore_rec 20:可睡眠锁:读写信号量rwsem_rec 21:可睡眠锁:完成...

    9.docx An Introduction to Kernel Synchronization

    1. **自旋锁**(Spinlock):当一个线程持有自旋锁时,其他试图获取该锁的线程会循环检查锁的状态,直到锁被释放。这种方式适用于短时间的临界区,因为线程不会睡眠,而是持续检查锁的可用性。 2. **信号量**...

    Linux操作系统课程指导:Ch910KernelSynchronization.pptx

    完成变量(Completion Variables)、顺序锁(Sequential Locks)、预判禁用(Preemption Disabling)以及排序与屏障(Ordering & Barriers)也是内核同步的重要组成部分,它们用于确保操作的正确顺序和避免数据竞争...

    Java很好的学习笔记4 无锁.md,学习代码

    - "并发编程.pdf"和"并发编程_应用.pdf"可能是全面的并发编程教程,涵盖了Java内存模型、线程池、信号量、屏障、条件变量等更多高级并发概念和技术。 学习这些内容,有助于开发者深入理解Java并发编程,提升在多...

Global site tag (gtag.js) - Google Analytics