一、互斥量
互斥量控制每次只有一个线程获得互斥量,执行操作,其他调用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_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用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()就是鸣响的起跑枪。