当控件的多个线程共享统一内存时,咱们须要肯定各个线程访问到的数据的一致性。在cpu结构中,修改操做由多个内存读写周期(memory cycle),而在这些内存周期之间, 有可能会发生其余线程的内存读操做,这样就会产生多线程之间的数据一致性问题。编程
互斥锁 mutex多线程
咱们能够经过线程互斥锁接口(pthreads mutual-exclusion interfaces)来保证同一时间只有一个线程访问咱们的数据。一个mutex变量使用pthread_mutex_t数据类型来表示。在咱们使用这个变量前,咱们必须使用常量PTHREAD_MUTEX_INITIALIZER或调用pthread_mutex_init对其进行初始化。若是咱们动态的分配mutex(经过调用pthread_mutex_init),咱们须要在释放内存前调用pthread_mutex_destroy。架构
1 #include <pthread.h> 2 3 /* Both return 0 if OK, error number on failure */ 4 int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr); 5 6 int pthread_mutex_destroy(pthread_mutex_t* mutex);
能够经过 pthread_mutex_lock对mutex加锁,若是mutex已经被锁住,那么其余对mutex进行加锁的线程会被阻塞,直到mutex被解锁。能够经过pthread_mutex_unlock来解锁mutex。函数
1 #include <pthread.h> 2 3 /* return 0 if OK, error number on failure */ 4 int pthread_mutex_lock(pthread_mutex_t* mutex); 5 6 /* 7 若是mutex处于unlocked状态,此方法会对mutex加锁并返回0, 8 不然返回EBUSY,不会对mutex加锁 9 */ 10 int pthread_mutex_trylock(pthread_mutex_t* mutex); 11 12 /* return 0 if OK, error number on failure */ 13 int pthread_mutex_unlock(pthread_mutex_t* mutex);
避免死锁性能
一个线程若是试图对同一个mutex进行连续两次加锁,那么它将处于死锁状态。而确实有那么几种明显的使用mutex会产生死锁。举例来讲,当咱们程序中有多个mutex的时候,若是咱们让一个线程锁住第一个mutex,而后对第二个mutex进行加锁,而与此同时若是另外一个线程锁住第二个mutex并试图对第一个mutex进行加锁,那么此时程序就会进入死锁状态。spa
咱们能够经过消息的控制对mutex的加锁状态来避免死锁。若是咱们全部的线程对全部的mutex都保持同一种加锁顺序,那么程序中永远不会出现加锁现象。而后有些时候,因为程序的架构问题咱们没法保证加锁顺序,此时咱们就必须采起一些其余的措施。在这种状况下咱们应该先释放掉以加锁的mutex,而后从新尝试加锁。这种状况下咱们可使用pthread_mutex_trylock,若是pthread_mutex_trylock成功咱们能够继续进行其余操做,不然咱们应该释放掉以加锁的mutex,清理程序,而后在重试加锁。线程
可使用pthread_mutex_timedlock方法绑定加锁阻塞时间,若是规定时间内没有成功对mutex加锁,它返回ETIMEOUT。rest
1 #include <pthread.h> 2 #include <time.h> 3 4 /* tspr is absolute time, Return 0 if OK, error number on failure. */ 5 int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex, const struct timespec* restrict tsptr);
读写锁code
读写锁也称为共享锁,它与mutex类似,但他们提供了对并行机制更高精度的控制。对于mutex来说,他只有两种状态:locked和unlocked,而且,同一时间内只有一个线程能够锁住它。而对于读写锁来讲,他们有三种状态:读锁状态(locked in read mode),写锁状态(locked in write mode)和解锁状态(unlocked)。同一时间只有一个线程可让读写锁处于写锁状态,而同一时间能够有多个线程让读写锁处于读锁状态。对象
当一个读写锁处于写锁状态时,全部尝试对其加锁的线程都会处于阻塞状态,知道读写锁的写锁状态被解除。当读写锁处于读锁状态时,全部对其进行写锁操做的线程都会被阻塞,可是对其进行读锁操做的线程不会被阻塞。一般,当有对处于读写锁进行写锁操做的线程被阻塞时,其余队此读写锁进行读操做的线程也会被阻塞。
读写锁在使用前必须初始化,一样的,在释放内存钱必须销毁。也可使用常量PTHREAD_RWLOCK_INITIALIZER初始化rwlock。
1 #include <pthread.h> 2 3 /* All return 0 if OK, error number on failure */ 4 int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr); 5 int pthread_rwlock_destroy(pthread_rwlock_t* rwlock); 6 7 int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock); 8 int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock); 9 /* unlock any type of rwlock */ 10 int pthread_rwlock_unlock(pthread_rwlock_t* rwlock); 11 12 int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock); 13 int pthread_rwlock_truwrlock(pthread_rwlock_t* rwlock);
rwlock with timeouts
1 #include <pthread.h> 2 #include <time.h> 3 4 int pthread_rwlock_timedrdlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict tsptr); 5 6 int pthread_rwlock_timedwrlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict tsptr);
条件变量(Condition Variables)
条件变量是另外一种线程同步机制,他们提供了线程交互点。当与mutex一块儿使用时, 条件变脸能够是线下以自由竞争的方式来等待任意条件的发生。这个条件自己是被一个mutex保护的。线程在改变条件状态以前必须对这个mutex加锁,其余线程在获取到这个mutex权限前不会知道这个变化,由于mutex必须处于加锁状态才能读取到条件的值。
条件变量在使用前必须进行初始化,咱们能够经过常量PTHREAD_COND_INITAILIZER或者调用pthread_cond_init来初始化条件变量, 若条件变量是动态分配的,那么我么须要使用pthread_cond_destroy来释放条件变量。
1 #include <pthread.h> 2 3 /* Both return 0 if OK, error number on failure */ 4 int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr); 5 6 int pthread_cond_destroy(pthread_cond_t* cond);
wait for condition to be true
1 #include <pthread.h> 2 3 int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex); 4 5 int pthread_cond_timedwait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex, constr struct timespec* restict tsptr);
mutex用于保护条件,以防止多个线程调用pthread_cond_wait的竞态条件。调用线程在调用前须要对mutex加锁,pthread_cond_wait会自动将调用线程放到此条件的等待线程列表中而后等待条件的发生并解锁mutex。当phtread_cond_wait返回时(正常状况是条件被知足时),mutex被从新置位加锁状态。在pthread_cond_wait或phread_cond_timedwait方法返回时,线程须要从新检测条件,由于其余线程在此时可能会更改了条件, 所以一般咱们将等待放入循环中。
有两个函数能够通知条件已知足,pthread_cond_signal能够唤醒至少一个条件等待线程,而pthread_cond_broadcast会唤醒全部的条件等待线程。
1 #include <pthread.h> 2 3 int pthread_cond_signal(pthread_cond_t* cond); 4 int pthread_cond_broadcast(pthread_cond_t* cond);
自旋锁(spin locks)
自旋锁与互斥锁类似,只不过它不是以休眠的方式阻塞进程,而是经过busy-waiting(spinning)知道获取到锁权限。自旋锁一般用于锁住状态时间较短和线程不肯遭受调度成本的状况。自旋锁一般用于实现其余类型锁的底层原型。依赖于系统结构,他们能够经过test-and-set指令高效的实现。尽管高效,他们会致使浪费CPU资源。
1 #include <pthread.h> 2 3 int pthread_spin_init(pthread_spinlock_t* lock, int pshared); 4 int pthread_spin_destroy(pthread_spinlock_t* lock); 5 6 int pthread_spin_lock(pthread_spinlock_t* lock); 7 int pthread_spin_trylock(pthread_spinlock_t* lock); 8 int pthread_spin_unlock(pthread_spinlock_t* lock);
Barriers
Barriers 是一种同步机制,它能够用于并行环境下协调多线程工做。barriers容许每一个线程都等待直到全部协调线程都达到某个点,而后从它阻塞的地方继续执行。pthread_jion就是一种形式的barrier——它使一个线程等待直到另外一个线程退出。Barrier对象要比他更通用一些,他们容许任意数量的线程等待直到全部的线程完成执行,但线程没必要退出。
1 #include <pthread.h> 2 3 int pthread_barrier_init(pthread_barrier_t* restrict barrier, const pthread_barrierattr_t* restrict attr, unsigned int count); 4 int pthread_barrier_destroy(pthread_barrier_t* barrier);
count参数用于指定在全部线程容许继续执行前必须达到barrier的线程数量。
咱们使用pthread_barrier_wait函数来指示此线程以完成他的工做并准备好等待其余线程遇上进度。
#include <pthread.h> /* Rturns 0 or PTHREAD_BARRIER_SERIAL_THREAD if OK, error number on failure */ int pthread_barrier_wait(pthread_barrier_t* barrier);
调用pthread_barrier_wait的线程会被置于休眠状态若是若是barrier数量未达到init时设置的数量。若是调用线程是最后一个线程,即barrier数量达到了初始化是设置的数量,此时全部的线程都会被唤醒。
总结
线程基本同步机制有 mutex、rwlock、condlock、spin lock、barrier。mutex同一时间内只有一个线程处于locked状态;rwlock提供了对读写更精细的控制;condlock经过条件变量为多线程同步提供了线程交互点;spin lock是高效的mutex,可是浪费cpu性能,通常用户层编程用不到此类型的锁;barrier提供了一种良好的多线程协调机制;实际项目中咱们应该根据几种锁的特性来合理使用,并注意要避免死锁问题。