一、互斥量
互斥量控制每次只有一个线程获得互斥量,执行操作,其他调用lock的线程都会阻塞;互斥量适合一个进程内的多线程访问公共区域或代码段时使用。
// 互斥量初始化 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); // 互斥量初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 互斥量销毁 int pthread_mutex_destroy(pthread_mutex_t *mutex); // 加锁 int pthread_mutex_lock(pthread_mutex_t *mutex); //尝试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); // 解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); //在给定的绝对时间到达前加锁或者阻塞,到时间依然没有获取到互斥量,就返回错误 int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);
二、读写锁
pthread下的读写锁机制,也称为共享独占锁;当给资源加读锁时,其他想加读锁的线程都可以成功,但想加写锁的会阻塞,直到所有的读锁都释放。当资源被加上写锁时,其他任何类型的锁请求都会阻塞。读写锁适合读取次数比写次数多的场景。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr); int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
三、条件变量
条件变量是一种同步机制,允许线程挂起,知道共享数据上的某些条件得到满足。条件便利上的基本操作有:触发条件(当条件变为true)时;等待条件,挂起线程直到其他线程触发条件。条件变量要和互斥量相联结,以避免出现条件竞争--一个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件。
// 条件变量初始化,不能由多个线程同时初始化一个条件变量。 int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr); // 条件变量初始化,不能由多个线程同时初始化一个条件变量。 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 释放条件变量 int pthread_cond_destroy(pthread_cond_t *cv); // 阻塞在条件变量上 int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex); // 阻塞直到指定时间 int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mp, const structtimespec * abstime); // 解除在条件变量上的阻塞 int pthread_cond_signal(pthread_cond_t *cv); // 释放阻塞的所有线程 int pthread_cond_broadcast(pthread_cond_t *cv);
条件变量在使用时需要注意以下几点:
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
pthread_mutex_lock(); while (condition_is_false) { pthread_cond_wait(); } pthread_mutex_unlock();
int pthread_cond_signal(pthread_cond_t *cv);函数被用来释放被阻塞在指定条件变量上的一个线程。必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。
四、信号量
五、自旋锁
自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。
// 自旋锁初始化 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);
六、线程栅栏
线程屏障是一种线程同步机制,可以让所有线程达都达到某种状态后,再开始进行后续动作。
// 初始化一个障碍,其中计数为count int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count); // 等待 int pthread_barrier_wait(pthread_barrier_t *barrier); // 销毁 int pthread_barrier_destroy(pthread_barrier_t *barrier);
pthread_barrier_*其实只做且只能做一件事,就是充当栏杆(barrier意为栏杆。形象的说就是把先后到达的多个线程挡在同一栏杆前,直到所有线程到齐,然后撤下栏杆同时放行。1)init函数负责指定要等待的线程个数;2)wait()函数由每个线程主动调用,它告诉栏杆“我到起跑线前了”。wait()执行尾栏杆会检查是否所有人都到栏杆前了,如果是,栏杆就消失所有线程继续执行下一句代码;如果不是,则所有已到wait()的线程停在该函数不动,剩下没执行到wait()的线程继续执行;3)destroy函数释放init申请的资源。
使用场景:这种“栏杆”机制最大的特点就是最后一个执行wait的动作最为重要,就像赛跑时的起跑枪一样,它来之前所有人都必须等着。所以实际使用中,pthread_barrier_*常常用来让所有线程等待“起跑枪”响起后再一起行动。比如我们可以用pthread_create() 生成100 个线程,每个子线程在被create出的瞬间就会自顾自的立刻进入回调函数运行。但我们可能不希望它们这样做,因为这时主进程还没准备好,和它们一起配合的其它线程还没准备好,我们希望它们在回调函数中申请完线程空间、初始化后停下来,一起等待主进程释放一个“开始”信号,然后所有线程再开始执行业务逻辑代码。
解决方案:为了解决上述场景问题,我们可以在init时指定n+1个等待,其中n是线程数。而在每个线程执行函数的首部调用wait()。这样100个pthread_create()结束后所有线程都停下来等待最后一个wait()函数被调用。这个wait()由主进程在它觉得合适的时候调用就好。最后这个wait()就是鸣响的起跑枪。