1.创建和销毁互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr); int pthread_mutex_destroy(pthread_mutex_t *mutex);
不能拷贝互斥量变量,因为使用拷贝的互斥量是不确定的。可以拷贝指向互斥量的指针。
宏PTHREAD_MUTEX_INITIALIZER来声明具有默认属性的静态互斥量。如果需要初始化一个非缺省属性的互斥量,必须使用动态初始化(malloc)。
不需要释放一个使用PTHREAD_MUTEX_INITIALIZER宏静态初始化的互斥量。
2.加锁和解锁互斥量
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
当调用线程已经锁住互斥量之后,就不能再加锁该互斥量。试图这样做的结果可能是返回错误(EDEADLK),或者可能陷入“自死锁”,使不幸的线程永远等待下去。
pthreads提供了pthread_mutex_trylock函数,当调用互斥量已被锁住时调用该函数将返回错误代码EBUSY。
3.死锁发生的4个条件:(1)对资源的访问是独占的(2)线程在已经占有一个资源时继续请求其他资源(3)所有线程都不放弃已经占有的资源(4)线程对资源的请求形成了一个环,其中每个资源都被一个线程所占有,而每个线程都在请求另一个线程所占有的资源。
4.饥饿:由于一个正在运行的线程必须是拥有最高优先级的就绪线程,如果一个高优先级线程总是处于就绪状态,则所有其他优先级比它低的线程就永远不会运行。
5.针对死锁,考虑以下两种通用的解决办法:
(1)固定加锁顺序:所有同时加锁互斥量A和互斥量B的代码,必须首先加锁互斥量A,然后加锁互斥量B。
(2)试加锁和回退:在锁住某个集合中的第一个互斥量后,使用pthread_mutex_trylock来加锁集合中的其他互斥量,如果失败则将集合中的所有已加锁互斥量释放,并重新加锁。
“回退”意味着你以正常的方式锁住集合中的第一个互斥量,而调用pthread_mutex_trylock函数有条件的加锁集合中其他互斥量。如果pthread_mutex_trylock返回EBUSY,则你必须释放已经拥有的所有属于该集合的互斥量并重新开始。
6.在一个条件变量上等待会导致以下原子操作:释放相关互斥量,等待其他线程发给该条件变量的信号(唤醒一个等待者)或广播该条件变量(唤醒所有等待者)。当等待条件变量时,互斥量必须始终锁住;当线程从条件变量等待中醒来时,它重新继续锁住互斥量。
7.创建和释放条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *condattr); int pthread_cond_destroy(phtread_cond_t *cond);
永远不要拷贝条件变量,因为使用条件变量的备份是不可知的。可以传递条件变量的指针以及不同函数和线程可以使用它来同步。
只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY,注销一个条件变量需要调用pthread_cond_destroy。
不必释放一个通过PTHREAD_COND_INITIALIZER宏静态初始化的条件变量。
8.等待条件变量
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);
(1)当线程等待条件变量时,它必须将相关互斥量锁住。记住,在阻塞线程之前,条件变量等待操作将解锁互斥量;而在重新返回线程之前,会再次锁住互斥量。
(2)被阻塞的线程可以被pthread_cond_signal,pthread_cond_broadcast函数唤醒,也可以被信号中断后被唤醒。
(3)pthread_cond_timedwait函数到了一定的时间,即使条件变量未发生也会解除阻塞。函数返回时,相应的互斥量往往是锁定的,即使函数出错返回。
(4)pthread_cond_wait函数的返回并不意味着条件变量的值一定发生了变化,必须重新检查条件的值,pthread_cond_wait函数返回时,相应的互斥量将当前线程锁定,即使是函数出错返回。
(5)等待条件有两种方式:无条件等待pthread_cond_wait和计时等待pthread_cond_timedwait函数,其中计时等待方式如果在给定时刻前条件没有满足,返回ETIMEOUT,结束等待,其中参数expiration以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
9.一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或者所有线程被唤醒,接着都试图再次占有相应的互斥锁。
10.阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait函数返回之前,条件变量的值都可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。
11.记住,线程在从条件变量返回之前总是锁住互斥量,即使是等待超时(pthread_cond_timedwait)。
12.唤醒条件变量等待线程
int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);
pthreads提供了两种方式唤醒等待的线程:一个是“发信号”,一个是“广播”。发信号只唤醒一个等待该条件变量的线程,而广播将唤醒所有等待该条件变量的线程。
当只有一个线程需要被唤醒来处理改变后的状态时,使用发信号。如果你为多个谓词条件使用一个条件变量,则不能使用发信号操作;因为你不能分辨是应该唤醒等待这个谓词条件的线程,还是唤醒唤醒等待那个谓词条件的线程。
13.唤醒丢失问题:
在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。唤醒丢失往往会在下面的情况下发生:
(1)一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
(2)另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
(3)没有线程正在处在阻塞等待的状态下。