目录
1 临界区
2 互斥量
3 创建与销毁
3.1创建互斥量
3.2销毁互斥量
4 加锁与解锁
4.1 加锁
4.2 解锁
4.3 线程范例
5 死锁和避免
5.1 产生死锁的四个必要条件:
5.2处理死锁的基本方法
6 条件变量
6.1 创建与销毁
6.2 等待与通知
6.3 条件变量通知例程
临界区是必须以互斥方式执行的代码段,也就是说在临界区的范围内只能有一个活动的执行线程。
互斥量(Mutex),又称为互斥锁,是一种用来保护临界区的特殊变量,它可以处于锁定(locked)状态,也可以处于解锁(unlocked)状态:
如果互斥锁是锁定的,就是某个特定的线程正持有这个互斥锁;
如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态。
每个互斥锁内部有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时,如果某个线程试图获取这个互斥锁,那么这个线程就可以得到这个互斥锁而不会阻塞;当互斥锁处于锁定状态时,如果某个线程试图获取这个互斥锁,那么这个线程将阻塞在互斥锁的等待队列内。
pthreads 使用 pthread_mutex_t 类型的变量来表示互斥量,同时在使用互斥量进行同步前需要先对它进行初始化,可以用静态或动态的方式对互斥量进行初始化。
(1) 静态初始化
对于静态分配的 pthread_mutex_t 变量来说,只要将 PTHREAD_MUTEX_INITIALIZER赋给变量就行了。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
(2)动态初始化
对于动态分配或者不使用默认属性的互斥变量来说,需要调用 pthread_mutex_int()函数来执行初始化工作。pthread_mutex_int()函数原型如下:
int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr);
参数 mutex 是一个指向要初始化的互斥量的指针;参数 attr 传递 NULL 来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量。函数成功返回 0,否则返回一个非 0 的错误码,
下表列出 pthread_mutex_init 出错的错误码。
错误码 | 出错描述 |
EAGAIN | 系统缺乏初始化互斥量所需的非内存资源 |
ENOMEM | 系统缺乏初始化互斥量所需的内存资源 |
EPERM | 调用程序没有适当的优先级 |
销毁互斥量使用 pthread_mutex_destroy()函数,原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数 mutex 指向要销毁的互斥量。
线程试图锁定互斥量的过程称之为加锁。
pthreads 中有两个试图锁定互斥量的函数,pthread_mutex_lock()和 pthread_mutex_trylock()。 pthread_mutex_lock()函数会一直阻塞到互斥量可用为止,而 pthread_mutex_trylock()则尝试加锁,但通常会立即返回。函数原型如下:
int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_trylock(pthread_mutex_t * mutex);
参数 mutex 是需要加锁的互斥量。函数成功返回 0,否则返回一个非 0 的错误码,其中在另一个线程已持有锁的情况下,调用 pthread_mutex_trylock()函数时错误码为 EBUSY.
解锁是线程将互斥量由锁定状态变为解锁状态。
pthread_mutex_unlock()函数用来释放指定的互斥量。函数原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数 mutex 是需要解锁的互斥量。函数成功返回 0,否则返回一个非 0 的错误码。
程序清单是使用互斥量来保证多线程同时输出顺序的例子,互斥量能保证只有获取资源的线程打印完,别的线程才能打印,从而避免了打印乱序的问题。
#include
#include
#include
#include
#include
pthread_t tid[2];
pthread_mutex_t lock;
void * doPrint(void *arg)
{
int id=(long)arg;
int i=0;
pthread_mutex_lock(&lock); //使用互斥量保护临界区
printf("job %d started.\n",id);
for(i=0;i<5;i++)
{
printf("job %d printing\n",id);
usleep(10);
}
printf("job %d finished\n",id);
pthread_mutex_unlock(&lock);
return NULL;
}
int main(void)
{
long i=0;
int err;
if(pthread_mutex_init(&lock,NULL)!=0)
{
printf("\n Mutex init failed\n");
return 1;
}
while(i<2)
{
err=pthread_create(&(tid[i]),NULL,&doPrint,(void *)i);
if(err!=0)
{
printf("Can't create thread:[%s]",strerror(err));
}
i++;
}
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_mutex_destroy(&lock);
return 0;
}
程序输出:
job 0 started.
job 0 printing
job 0 printing
job 0 printing
job 0 printing
job 0 printing
job 0 finished
job 1 started.
job 1 printing
job 1 printing
job 1 printing
job 1 printing
job 1 printing
job 1 finished
方法 | 资源分配策略 | 各种模式 | 主要优点 | 主要缺点 |
预防 | 保守的;宁可资源闲置(在机制上使死锁条件不成立) | 一次分配所有的资源(条件2) | 适用于突发式处理进程;不必剥夺 | 效率低,进程初始化时间长 |
可剥夺资源(条件3) | 适用于状态可以保存和恢复的资源 | 剥夺次数过多;多次对资源重新启动 | ||
资源有序分配(条件4) | 可以在编译时进行检查 | 不便灵活的重新申请资源 | ||
避免 | 在运行时判断是否可能死锁 | 寻找安全的运行顺序 | 不必进行剥夺 | 使用条件:必须知道将来资源的需求,进程可能会长时间阻塞 |
检测 | 宽松的,只要允许就分配资源 | 定期检查死锁是否以及发生 | 不延长进程初始化时间,允许对死锁进行现场处理 | 通过剥夺死锁,造成损失 |
(1)创建条件变量
Pthreads 用 pthread_cond_t 类型的变量来表示条件变量。程序必须在使用 pthread_cond_t变量之前对其进行初始化。
静态初始化
对于静态分配的变量可以简单地将 PTHREAD_COND_INITIALIZER 赋值给变量来初始化默认行为的条件变量。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
动态初始化
对动态分配或者不使用默认属性的条件变量来说可以使用 pthread _cond_init()来初始化。函数原型如下:
int pthread_cond_init (pthread_cond_t *restrict cond,const pthread_condattr_t * restrict attr) ;
参数 cond 是一个指向需要初始化 pthread_cond_t 变量的指针,参数 attr 传递 NULL 值时,pthread_cond_init()将 cond 初始化为默认属性的条件变量。函数成功将返回 0;否则返回一个非 0 的错误码。
静态初始化程序通常比调用 pthread_cond_init()更有效,而且在任何线程开始执行之前,确保变量被执行一次。
(2)销毁条件变量
函数 pthread_cond_destry( ) 用来销毁它参数指出的条件变量,函数原型如下:
int pthread_cond_destroy ( pthread_cond_t *cond );
函数成功调用返回 0 ,否则返回 非0的错误码。
(1)等待
条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。
条件等待函数有两个pthread_cond_wait()和pthread_cond_timedwait(),函数原型如下:
int pthread_cond_wait (pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait (pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
pthread_cond_wait()函数在条件不满足时将一直等待,而 pthread_cond_timedwait()将只等待一段时间。
参数 cond 是一个指向条件变量的指针,参数 mutex 是一个指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量。pthread_timedwait()的第三个参数 abstime 是一个指向返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出,abstime 是个绝对时间,而不是时间间隔。
以上函数成功调用返回 0,否则返回非 0 的错误码,其中 pthread_cond_timedwait() 函数如果 abstime 指定的时间到期,错误码为 ETIMEOUT。
(2)通知
当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个等待在条件变量等待队列中的线程。条件通知函数有 pthread_cond_signal()和 pthread_cond_broadcast()函数,其中 pthread_cond_signal 函数可以唤醒一个在条件变量等待队列等待的线程,而 pthread_cond_broadcast 函数可以所有在条件变量等待队列等待的线程。函数原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
参数 cond 是一个指向条件变量的指针。函数成功返回 0,否则返回一个非 0 的错误码。
#include
#include
#include
#include
#include
pthread_t tid[3];
int sum = 0;
pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化互斥量 */
pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER; /* 静态初始化条件变量 */
/* 使用互斥量保护临界变量 */
void * t1t2(void *arg) {
int i;
long id = (long)arg;
for (i = 0; i < 60; i++) {
pthread_mutex_lock(&sumlock);
sum++;
printf("t%ld: read sum value = %d\n", id + 1 , sum);
pthread_mutex_unlock(&sumlock);
if (sum >= 100)
pthread_cond_signal(&cond_sum_ready);/* 发送条件通知,唤醒等待线程 */
}
return NULL;
}
void * t3(void *arg) {
pthread_mutex_lock(&sumlock);
while(sum < 100)/* 不满足条件将一直等待 */
pthread_cond_wait(&cond_sum_ready, &sumlock);/* 等待条件满足 */
sum = 0;
printf("t3: clear sum value\n");
pthread_mutex_unlock(&sumlock);
return NULL;
}
int main(void)
{
int err;
long i;
for (i = 0; i < 2; i++) {
err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i);/* 创建线程 1 线程 2 */
if (err != 0) {
printf("Can't create thread :[%s]", strerror(err));
}
}
err = pthread_create(&(tid[2]), NULL, &t3, NULL);/* 创建线程 3 */
if (err != 0)
printf("Can't create thread :[%s]", strerror(err));
for (i = 0; i < 3; i++)
pthread_join(tid[i], NULL);
return 0;
}
输出图片:t1和t2线程计数到100,通知进程t3对sun进行清零。