同步

互斥量

#include <pthread.h>

pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER;
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

注意:

  1. 不能拷贝互斥量,可以拷贝指向互斥量的指针,这样可以使多个线程或函数共享互斥量来同步
  2. 应该在函数体外生命互斥量,若没有外部文件使用,就声明为静态类型;若有其他文件使用,就声明为外部类型,使用PTHREAD_MUTEX_INITIALIZER宏声明默认属性的静态互斥量。
  3. 使用malloc分配一个包含互斥量的数据结构时,用pthread_init_mutex来初始化,用...destory销毁,只能初始化一次。
  4. 将互斥量与要保护的数据联系在一起(定义在一个结构体中)
  5. 当没有线程在乎斥量上阻塞时,立即释放。在刚解锁互斥量的线程内若以后不再使用释放
  6. 互斥量较少时程序运行的较快,所以互斥量保护的临界区应该较大
  7. 如互斥量保护的代码包含无关的片段,应该把互斥量分解为几个较小的互斥量来提高性能
  8. 多线程同时访问队列时使用两个互斥量:一个保护队头,一个队列元素内的数据
  9. 多线程同时访问树形结构时,应为每个节点建立个互斥量

加锁互斥量

#include <pthread.h>

int pthread_mutex_tlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthreadd_mutex_t *mutex);

注意:

  1. pthread_mutex_trylock会尝试对互斥量加锁,如果该互斥量已经被锁住,函数调用失败,返回EBUSY,否则加锁成功返回0,线程不会被阻塞。
  2. 当调用线程使用pthread_mutex_lock加锁互斥量时,若互斥量被锁住,调用线程阻塞
  3. 如果调用线程将互斥量锁住,不能再加锁该互斥量,这样做会反悔错误EDEADLK,或陷入死锁
  4. 原子性:当多个线程运行在多个处理器上,其他线程不会发现被破坏的不变量
  5. 固定加锁层次:当数据上需要使用两个独立的互斥量时,应该先加锁互斥量A再加锁互斥量B。
  6. 试加锁和回退:锁住某个集合中的第一个互斥量时用pthread_mutex_trylock来加锁集合中的其他互斥量,如果失败将集合中的以加锁互斥量释放
  7. 正常的工作模式是加锁与解锁顺序一致,若使用试加锁和回退算法,应该以与加锁相反的方向解锁互斥量,有利于减少回退操作性的可能
  8. 链锁:两个锁的作用范围相互交叠。当锁住一个互斥量后进入一个代码区,该区域需要另个互斥量,当锁住另个互斥量后第一个互斥量不再需要,就可以释放(用作遍历树形结构或链表,为每个节点创建一个互斥量,首先锁住队头或根节点,然后找到期望的节点锁住他,然后释放根节点或队头互斥量);搜索某个节点时使用链锁(多个线程活跃在层次的不同部分时)。平衡或修剪树时使用层次锁。
  9. POSIX线程锁机制的Linux实现都不是取消点,因此,延迟取消类型的线程不会因收到取消信号而离开加锁等待。
  10. 线程在加锁后解锁前被取消,锁将永远保持锁定状态。因此如果在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。
  11. 锁机制不是异步信号安全的,也就是说,不应该在信号处理过程中使用互斥锁,否则容易造成死锁。

互斥锁属性

//获得/修改共享互斥量属性
pthread_mutexattr_t attr;  
int pthread_mutexattr_init(pthread_mutexattr_t *attr);  
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);  
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr,int *pshared);  
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);

pshared的取值

  1. THREAD_PROCESS_SHARED:那么由这个属性对象创建的互斥锁将被保存在共享内存中,可以被多个进程中的线程共享。
  2. PTHREAD_PROCESS_PRIVATE:那么只有和创建这个互斥锁的线程在同一个进程中的线程才能访问这个互斥锁。

互斥锁类型

//获得/修改类型互斥量属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int kind);  
int pthread_mutexattr_gettype(pthread_mutexattr_t *attr,int *kind);
  1. PTHREAD_MUTEX_NORMAL,不进行deadlock detection(死锁检测)。企图进行relock这个mutex会导致deadlock.如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,结果是不未知的。
  2. PTHREAD_MUTEX_ERRORCHECK,那么将进行错误检查。如果一个线程企图对一个已经锁住的mutex进行relock,将返回一个错误。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。
  3. PTHREAD_MUTEX_RECURSIVE,mutex会有一个锁住次数(lock count)的概念。当一个线程成功地第一次锁住一个mutex的时候,锁住次数(lock count)被设置为1,每一次一个线程unlock这个mutex的时候,锁住次数(lock count)就减1。当锁住次数(lock count)减少为0的时候,其他线程就能获得该mutex锁了。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误,如果一个线程对这种类型的互斥锁重复上锁,不会引起死锁,一个线程对这类互斥锁的多次重复上锁必须由这个线程来重复相同数量的解锁,这样才能解开这个互斥锁,别的线程才能得到这个互斥锁。如果试图解锁一个由别的线程锁定的互斥锁将会返回一个错误代码。如果一个线程试图解锁已经被解锁的互斥锁也将会返回一个错误代码。这种类型的互斥锁只能是进程私有的(作用域属性为PTHREAD_PROCESS_PRIVATE)。
  4. PTHREAD_MUTEX_DEFAULT,企图递归的获取这个mutex的锁的结果是不确定的。unlock一个不是被调用线程锁住的mutex的结果也是不确定的。企图unlock一个未被锁住的mutex导致不确定的结果。

互斥锁协议属性

int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);  
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol);  
  1. PTHREAD_PRIO_NONE:线程的优先级和调度不会受到互斥锁拥有权的影响。
  2. PTHREAD_PRIO_INHERIT:当高优先级的等待低优先级的线程锁定互斥量时,低优先级的线程以高优先级线程的优先级运行。这种方式将以继承的形式传递。当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级。(“优先级继承”意味着,当一个线程在由另一个低优先级线程拥有的互斥量上等待时,后者的优先级将被增加到等待线程的优先级.)
  3. PTHREAD_PRIO_PROTECT:拥有该类型的互斥量的线程将以自己的优先级和它拥有的互斥量的线程将以自己的优先级和它拥有的互斥量的优先级较高者运行,其他等待该线程拥有的锁得线程对该线程的调度优先级没有影响。

  PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 只有在采用实时调度策略SCHED_FIFO 或SCHED_RR的优先级进程内可用。

  一个线程可以同时拥有多个混合使用PTHREAD_PRIO_INHERIT 和PTHREAD_PRIO_PROTECT协议属性初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行。pthread_mutexattr_getprotocol可用来获取互斥锁属性对象的协议属性。

互斥锁对象优先级属性

int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, int prioceiling, int *oldceiling);  
int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *attr, int *prioceiling);

  prioceiling指定已初始化互斥锁的优先级上限。优先级上限定义执行互斥锁保护的临界段时的最低优先级。prioceiling 位于SCHED_FIFO 所定义的优先级的最大范围内。要避免优先级倒置,请将prioceiling 设置为高于或等于可能会锁定特定互斥锁的所有线程的最高优先级。oldceiling 用于返回以前的优先级上限值。
  pthread_mutex_setprioceiling可更改互斥锁mutex的优先级上限prioceiling。
  pthread_mutex_setprioceiling可锁定互斥锁(如果未锁定的话),或者一直处于阻塞状态,直到它成功锁定该互斥锁,更改该互斥锁的优先级上限并将该互斥锁释放为止。锁定互斥锁的过程无需遵循优先级保护协议。
  如果 pthread_mutex_setprioceiling成功,则将在 old_ceiling 中返回以前的优先级上限值。如果pthread_mutex_setprioceiling失败,则互斥锁的优先级上限保持不变。pthread_mutex_getprioceiling会返回mutex 的优先级上限prioceiling。

  注意:“优先级上限”协议意味着当一个线程拥有互斥量时,它将以指定的优先级运行。

避免死锁

  该函数允许线程阻塞特定时间,如果加锁失败就会返回ETIMEDOUT

#include <pthread.h>
#include <time.h>

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timesec *restrict tsptr);

互斥锁的优先级

#include <pthread.h>
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr,int *protocol);

  attr 指示以前调用 pthread_mutexattr_init() 时创建的互斥锁属性对象。protocol 可定义应用于互斥锁属性对象的协议。

  protocol 可以是以下值之一:PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT。

  1. PTHREAD_PRIO_NONE,线程的优先级和调度不会受到互斥锁拥有权的影响。
  2. PTHREAD_PRIO_INHERIT,当高优先级的等待低优先级的线程锁定互斥量时,低优先级的线程以高优先级线程的优先级运行。这种方式将以继承的形式传递。当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级。(“优先级继承”意味着,当一个线程在由另一个低优先级线程拥有的互斥量上等待时,后者的优先级将被增加到等待线程的优先级.),此协议值(如 thrd1)会影响线程的优先级和调度。如果更高优先级的线程因 thrd1 所拥有的一个或多个互斥锁而被阻塞,而这些互斥锁是用 PTHREAD_PRIO_INHERIT 初始化的,则 thrd1 将以高于它的优先级或者所有正在等待这些互斥锁(这些互斥锁是 thrd1指所拥有的互斥锁)的线程的最高优先级运行。如果 thrd1 因另一个线程 (thrd3) 拥有的互斥锁而被阻塞,则相同的优先级继承效应会以递归方式传播给 thrd3。使用 PTHREAD_PRIO_INHERIT 可以避免优先级倒置。低优先级的线程持有较高优先级线程所需的锁时,便会发生优先级倒置。只有在较低优先级的线程释放该锁之后,较高优先级的线程才能继续使用该锁。设置 PTHREAD_PRIO_INHERIT,以便按与预期的优先级相反的优先级处理每个线程。如果为使用协议属性值 PTHREAD_PRIO_INHERIT 初始化的互斥锁定义了 _POSIX_THREAD_PRIO_INHERIT,则互斥锁的属主失败时会执行以下操作。属主失败时的行为取决于 pthread_mutexattr_setrobust_np() 的 robustness 参数的值,1.解除锁定互斥锁。2.互斥锁的下一个属主将获取该互斥锁,并返回错误 EOWNERDEAD。3.互斥锁的下一个属主会尝试使该互斥锁所保护的状态一致。如果上一个属主失败,则状态可能会不一致。如果属主成功使状态保持一致,则可针对该互斥锁调用 pthread_mutex_init() 并解除锁定该互斥锁。注:1.如果针对以前初始化的但尚未销毁的互斥锁调用 pthread_mutex_init(),则该互斥锁不会重新初始化。2.如果属主无法使状态保持一致,请勿调用 pthread_mutex_init(),而是解除锁定该互斥锁。在这种情况下,所有等待的线程都将被唤醒。以后对 pthread_mutex_lock() 的所有调用将无法获取互斥锁,并将返回错误代码 ENOTRECOVERABLE。现在,通过调用 pthread_mutex_destroy() 来取消初始化该互斥锁,即可使其状态保持一致。调用 pthread_mutex_init() 可重新初始化互斥锁。3.如果已获取该锁的线程失败并返回 EOWNERDEAD,则下一个属主将获取该锁及错误代码 EOWNERDEAD。
  3. PTHREAD_PRIO_PROTECT,当线程拥有一个或多个使用 PTHREAD_PRIO_PROTECT 初始化的互斥锁时,此协议值会影响其他线程(如 thrd2)的优先级和调度。thrd2 以其较高的优先级或者以 thrd2 拥有的所有互斥锁的最高优先级上限运行。基于被 thrd2 拥有的任一互斥锁阻塞的较高优先级线程对于 thrd2 的调度没有任何影响。有该类型的互斥量的线程将以自己的优先级和它拥有的互斥量的线程将以自己的优先级和它拥有的互斥量的优先级较高者运行,其他等待该线程拥有的锁得线程对该线程的调度优先级没有影响,如果某个线程调用 sched_setparam() 来更改初始优先级,则调度程序不会采用新优先级将该线程移到调度队列末尾。线程拥有使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥锁。线程解除锁定使用 PTHREAD_PRIO_INHERIT 或 PTHREAD_PRIO_PROTECT 初始化的互斥锁

  一个线程可以同时拥有多个混合使用 PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 初始化的互斥锁。在这种情况下,该线程将以通过其中任一协议获取的最高优先级执行PTHREAD_PRIO_INHERIT 和 PTHREAD_PRIO_PROTECT 只有在采用实时调度策略SCHED_FIFO 或SCHED_RR的优先级进程内可用。

优先级继承互斥量

  当一个线程锁住互斥量时,线程的优先级就被互斥量控制,当另外的线程在那个互斥量上阻塞时,他会查看拥有互斥量的线程优先级,如果拥有互斥量的线程比试图在互斥量上阻塞的线程优先级低,则拥有互斥量的线程的优先级将被提升到阻塞线程的优先级。

  除非等待的线程也被抢占,提高优先级确保拥有互斥量的线程不能被抢占;拥有互斥量的线程代表的是高优先级工作的线程,当线程互斥量解锁时,线程的优先级被自动降到他原来的优先级,高优先级等待线程将被唤醒,如果又有一个高优先级线程在互斥量上阻塞,拥有互斥量的线程将再次增加优先级。

条件变量

#include <pthread.h>
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *condattr);
int pthread_cond_destroy(pthread_cond_t *cond);
  1. 条件变量总是返回锁住的互斥量,是用来通知共享数据的状态信息
  2. 条件变量与互斥量相关,也于互斥量保护的共享数据相关的信号机制
  3. 在一个条件变量上等待会导致一下原子操作:释放相关的互斥量,等待其他线程发送给该条件变量信号(唤醒一个等待者)或广播该条件变量(唤醒所有等待者),等待条件变量时互斥量必须始终锁住,当线程从条件变量等待中醒来时,继续锁住互斥量(即:阻塞线程前,条件变量等待操作将解锁互斥量,重新返回线程前,会再次锁住互斥量)
  4. 条件变量的作用是发信号而不是互斥
  5. 互斥量不仅要与条件变量一起使用,还要单独使用(不能将互斥量作为条件变量的一部分创建);一个互斥量与多个条件变量相关(如:队列可以为空,也可以为满,设置两个条件变量让线程等待不同的条件,但只有一个互斥量来协调对队列头的访问)
  6. 一个条件变量应该与一个谓词相关
  7. 信号比广播有效(条件变量和谓词都是程序中的共享数据,他们被多个线程使用,可能同时使用,他们被相同的互斥量锁住,在没有锁住互斥量之前就发信号或广播条件变量似乎有可能的,更安全的方式是先锁住互斥量)
  8. 不能拷贝条件变量但可以传递条件变量的指针使不同函数和线程使用它来同步
  9. 若有其他文件需要使用该条件变量则声明为extern类型否则声明为static
  10. 可以将条件变量,互斥量和谓词声明在一个结构体中
  11. 在广播了该条件变量并唤醒了所有等待线程的线程内,确定不再有线程使用可释放该条件变量
  12. 线程从列表中删除了一个包含该条件变量的节点后,广播所有等待该条件变量的线程,此时释放条件变量是安全的
  13. 并发的等待同一个条件变量的线程必须制定同一个相关的互斥量,任何条件变量在特定时刻只能与一个互斥量相关联,互斥量可以与多个条件变量相关联

等待条件变量、唤醒等待线程

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,struct timespec *expiration);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
  1. 在锁住相关的互斥量之后和等待条件变量之前要测试谓词,如果线程发信号或广播一个条件变量时而没有线程等待该条件变量,则什么也没有发生,若在这之后有线程调用pthread_cond_wait,则将一直等待下去无视刚才广播的事实,这意味该线程永远不能被唤醒,因为在线程等待条件变量之前互斥量一直被锁住
  2. 因该在循环中等待条件变,来避免程序错误,处理机竞争和假唤醒
  3. 被拦截的唤醒:线程执行时异步的,从条件变量等待中唤醒包括加锁相应的互斥量。也就是说可能有多个线程被唤醒(被pthread_cond_broadcast唤醒),被唤醒的线程都“争着”去处理共享数据,但只有一个线程能处理到这个共享数据,等它处理完毕后,其他线程也会进入该临界区去处理共享数据,这个时候就会出现共享数据被多次处理的情况,我们的解决方法是在被唤醒后,在去检查一下谓词变量,看该共享数据是否已经被处理了。
  4. 松散的谓词:有时候谓词表示了“可能有工作”而不是“有工作”的意思。所以,当线程被唤醒,我们必须确认到底有没有工作。
  5. 假唤醒:在有些系统中,我们没有用pthread_cond_signal()或pthread_cond_broadcast()来唤醒用pthread_cond_wait()休眠的线程,但是在休眠的线程还是会被唤醒,在Linux中,被休眠的进程或线程,都有被信号(linux中的信号)打断的可能,也就是说,如果线程或进程在休眠中,会有被唤醒的可能,所以我们在有可能引起休眠的API调用返回后都会检查一下返回值是否为EINTR,如果是,那么线程或进程都将继续休眠。
  6. tiemdwait函数等待某个时间结束后会返回ETIMEOUT状态,时间是绝对时间。处理该错误之前要先检测谓词,若等待条件为真,那么等待长时间就不重要了。
  7. 不能用发信号代替广播,使用广播时因为等待线程会处理被拦截的唤醒,发信号与广播真正的区别是效率:广播唤醒额外的线程,这些线程检测到自己的谓词继续等待
  8. 当有个线程需要被唤醒来处理改变后的状态用发信号,如果多个谓词条件使用同一个条件变量,则不能是使用发信号,也不能用重发信号,如果向队列中增加一个元素,而且只有等待钙元素的线程在条件变量上阻塞,则可使用发信号,让其他线程继续等待,如果增加多个元素,可能要使用广播。

条件变量的属性

#include < pthread.h >

int pthread_condattr_init(pthread_condattr_t * attr );
int pthread_condattr_destroy(pthread_condattr_t * attr );

#ifdef _POSIX_THREAD_PROCESS_SHARED
int pthread_condattr_getpshared(pthread_condattr_t *attr,int *pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,int *pshared);
#endif // _POSIX_THREAD_PROCESS_SHARED
  1. pthread_condattr_destroy()函数应销毁条件变量属性对象; 实际上,对象变得未初始化。实现可能导致pthread_condattr_destroy()将attr引用的对象设置为无效值。可以使用pthread_condattr_init()重新初始化已销毁的attr属性对象在销毁之后引用对象的结果是未定义的。
  2. pthread_condattr_init()函数将初始化条件变量属性对象ATTR与所有实现中定义的属性的缺省值。
  3. 如果调用pthread_condattr_init()指定已初始化的attr属性对象,则结果未定义。
  4. 在使用条件变量属性对象初始化一个或多个条件变量之后,影响属性对象的任何函数(包括破坏)都不会影响任何先前初始化的条件变量。
  5. cattr 的数据类型为 opaque,其中包含一个由系统分配的属性对象。cattr 范围可能的值为 PTHREAD_PROCESS_PRIVATE 和 PTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE 是缺省值。条件变量属性必须首先由 pthread_condattr_destroy 重新初始化后才能重用。pthread_condattr_init() 调用会返回指向类型为 opaque 的对象的指针。如果未销毁该对象,则会导致内存泄漏。
  6. 这两个函数执行成功都返回0,返回其他值都是错误
  7. 为使用一个PTHREAD_PROCESS_SHARED条件变量,必须使用一个PTHREAD_PROCESS_SHARED互斥量,因为同步使用一个条件变量的两个线程必须使用一样的互斥量,等待一个条件变量会自动解锁然后加锁相关的互斥量,因此如果互斥量没有与PTHREAD_PROCESS_SHARED一起被创建,同步不会工作。

条件变量的范围

pthread_condattr_t cattr;
int pthread_condattr_setpshared(pthread_condattr_t *cattr, int pshared);//设置条件变量的范围
/* all processes */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
/* within a process */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);

int pthread_condattr_getpshared(const pthread_condattr_t *cattr,int *pshared);//获取条件变量的范围
//以上两个函数成功都返回0,返回其它任何值都失败

  pshared属性在共享内存中设置为 PTHREAD_PROCESS_SHARED,则其所创建的条件变量可以在多个进程中的线程之间共享。此行为与最初的 Solaris 线程实现中 mutex_init() 中的 USYNC_PROCESS 标志等效。如果互斥锁的 pshared 属性设置为 PTHREAD_PROCESS_PRIVATE,则仅有那些由同一个进程创建的线程才能够处理该互斥锁。PTHREAD_PROCESS_PRIVATE 是缺省值。PTHREAD_PROCESS_PRIVATE 所产生的行为与在最初的 Solaris 线程的 cond_init() 调用中使用 USYNC_THREAD 标志相同。PTHREAD_PROCESS_PRIVATE 的行为与局部条件变量相同。PTHREAD_PROCESS_SHARED 的行为与全局条件变量等效。

线程间的内存可见性

  每个线程都有独立的线程上下文,包括线程id,栈,栈指针,程序计数器,条件码和通用目的寄存器。每个线程和其他线程共享进程虚拟地址空间,由代码段,读/写数据,堆和所有的共享代码库和数据组成。共享打开的文件集合。

  1. 当线程调用pthread时,他所能看见的内存值也是它建立的线程能够看到的,任何在pthread_create之后向内存写入的数据可能不会被建立的线程看到,即使写操作发生在启动线程之前
  2. 当线程解锁互斥量时看到的内存数据,同样能被后来直接锁住(或通过等待条件变量锁住)相同互斥量的线程看到。同样,在解锁互斥量之后写入的数据不必被其他线程看见,即使写操作发生在其他线程锁住互斥量之前。
  3. 线程终止(或通过取消操作,或从启动函数中返回,或调用pthread_exit())时看到的内存数据,同样能够被链接该线程的其他线程(通过pthread_join())看到,终止后写入的数据不会被其他线程看到,即使写操作发生在连接之前
  4. 线程发信号或广播条件变量时看到的内存数据,同样可以被唤醒的其他线程看到,而在发信号或广播之后写入的数据不会被唤醒的线程看到,即使写操作发生在线程被唤醒之前。
  5. 线程寄存器变量不能被其他线程修改;线程分配的堆栈和堆空间是私有的,除非线程将指向该内存的指针传递给其他线程;任何放在register和auto变量中的数据可以再随后的某个时刻读取,就想完全同步的数据一样,每个线程自己是同步的,线程中共享的数据越少需要做的工作越多。

  从pthread_mutex_unlock()到pthread_mutex_lock()之间可视化,当线程b从pthread_mutex_lock()中返回时,他将和线程a调用pthread_mutex_unlock()看到同样变量的值。即相应的1和2

//线程1                                   
pthread_mutex_lock(&mutex); 
a=1;
b=2;
pthread_mutex_unlock(&mutex);
//线程2 
pthread_mutex_lock(&mutex);
local_a=a;
local_b=b;
pthread_mutex_unlock(&mutex);

  从pthread_mutex_unlock()到pthread_mutex_lock()之间可视化。当线程b从pthread_mutex_lock()中返回时,他将和线程a调用pthread_mutex_unlock()看到同样变量的值。即local_a相应1,local_b的值,因为他是在解锁互斥量之后写入的。

//线程1                                   
pthread_mutex_lock(&mutex); 
a=1;
pthread_mutex_unlock(&mutex);
b=2;
//线程2 
pthread_mutex_lock(&mutex);
local_a=a;
local_b=b;
pthread_mutex_unlock(&mutex);
  1. 线程的寄存器变量、线程分配的堆栈是私有的不能被其它线程看见。任何存放在register或auto中的变量可以在随后的某刻读取到。
  2. 如果线程设置了一个全局变量,然后创建一个线程读取该变量,则新线承看不到旧变量(只能看到改过的新值);如果创建一个新线承然后再设置变量值,新线承可能看不到新的变量值

读写锁

  1. 只要没有写模式下的加锁,任意线程都可以进行读模式下的加锁这个其实可以很好理解,因为单纯的读操作不改变访问的任何资源,多个线程都可以同时无影响的读取资源。
  2. 只有读写锁处于不加锁状态时,才能进行写模式下的加锁

  读写锁也称为共享-(shared-exclusive)独占锁,当读写锁以读模式加锁时,它是以共享模式锁住(即任何访问者都可以使用),当以写模式加锁时,它是以独占模式锁住(只有获得锁的人可以使用)

  当一个写锁被释放时,如果读数据写数据同时等待,读数据有优先访问权限

  读写锁的类型是在内存中分配的,当读写锁是在单个进程内的各个线程共享时(默认情况),这些变量可以在进程内;当这些变量是在某个共享内存区的进程间共享时(假设指定PTHREAD_PROCESS_SHARED),这些变量在共享内存中

与互斥锁的区别

  1. 互斥锁会把试图进入已保护的临界区的线程都阻塞,即同时只有一个线程持有锁
  2. 读写锁会根据当前进入临界区的线程是读还是写的属性来判断是否允许线程进入。
  3. 多个读者可以同时持有锁

初始化

/* 用于初始化读写锁,并可以设置读写锁属性,
* 一般默认为NULL,如果要修改写锁属性可以详细参考链接
* http://docs.oracle.com/cd/E19455-01/806-5257/6je9h032t/index.html
* PTHREAD_RWLOCK_INITIALIZER宏定义可以用来静态的分配初始一个读写锁,不需要错误检查,分配默认
* 的读写锁属性,和pthread_rwlock_init()指定NULL属性的效果是一致的。pthread_rwlock_destroy()
* 用于销毁读写锁。
*/
pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
int pthread_rwlock_init(pthread_rwlock_t * restrict lock,const pthread_rwlockattr_t * restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *lock);

init返回值

  1. 成功返回0
  2. [EAGAIN]:系统缺少资源来初始化读写锁
  3. [EBUSY]:尝试初始化一个已经初始化但还未销毁的锁.
  4. [EINVAL]:attr不可用.
  5. [ENOMEM]:内存资源不足

destroy返回值

  1. 成功返回0
  2. [EBUSY] 尝试销毁一个还锁着的锁
  3. [EINVAL] 指定的锁参数不可用

读锁

int pthread_rwlock_rdlock(pthread_rwlock_t *lock);
int pthread_rwlock_timedrdlock(pthread_rwlock_t * restrict lock,const struct timespec * restrict abstime);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *lock);
/* 读写锁读加锁有3个函数接口,pthread_rwlock_rdlock()请求一个读锁,
* 如果当前没有写线程持有锁或者没有写线程阻塞在取锁时则可以立刻获取到锁。否则请求锁的线程阻塞等
* 待,pthread_rwlock_timedrdlock() 执行同样的读加锁操作,只是有个超时时间,在指定abstime时间
*(超时指定的是绝对时间,不是相对时间)获取不到直接返回,其中abstime的类型为struct timespec
*/
struct timespec {
    time_t   tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};

/* pthread_rwlock_tryrdlock()执行同样加锁操作的非阻塞函数,获不到锁立即返回。
注:一个线程可能获取到多个并发的读锁,如果是这样的话,对每个加锁都要释放一次。
*/

  成功均返回0

  失败

  1. pthread_rwlock_tryrdlock():[EBUSY]其写锁正被持有或者写锁阻塞等待
  2. pthread_rwlock_timedrdlock():[ETIMEDOUT]加锁超时
  3. pthread_rwlock_rdlock(), pthread_rwlock_timedrdlock(), and pthread_rwlock_tryrdlock():[EAGAIN]获取锁的数量已经超过最大值,[EDEADLK]当前线程已经持有写锁,[EINVAL]指定的锁参数不可用

写锁

int pthread_rwlock_wrlock(pthread_rwlock_t *lock); //阻塞的形式获取一个写锁
int pthread_rwlock_timedwrlock(pthread_rwlock_t * restrict lock,const struct timespec * restrict abstime); //执行相同的取写锁的操作, 只是有个超时时间,
//在指定abstime绝度时间内获取不到直接返回
int pthread_rwlock_trywrlock(pthread_rwlock_t *lock); //执行同样加锁操作的非阻塞函数,获不到锁立即返回。

  成功均返回0

  失败

  1. The pthread_rwlock_trywrlock():[EBUSY]不能非阻塞的获得锁
  2. pthread_rwlock_timedwrlock():[ETIMEDOUT]加锁超时
  3. pthread_rwlock_wrlock(), pthread_rwlock_timedwrlock(), and pthread_rwlock_trywrlock():[EDEADLK]调用的线程已经持有读写锁了,[EINVAL]指定的锁参数不可用

解锁

int pthread_rwlock_unlock(pthread_rwlock_t *lock);// 改函数用来释放获取到的读写锁。

  成功均返回0

  失败

  1. [EINVAL]指定的锁参数不可用
  2. [EPERM]当前线程没有持有锁

posted on 2018-06-08 23:08  tianzeng  阅读(2017)  评论(0编辑  收藏  举报

导航