线程控制
一、线程限制
线程限制:
二、互斥量属性
#include <pthread.h>
//查询pthread_mutexattr_t结构,得到它的进程共享属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
//修改进程共享属性。
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
#include <pthread.h>
//获取健壮的互斥量属性的值
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);
//设置健壮的互斥量属性的值。
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);
//两个函数的返回值:若成功,返回0;否则,返回错误编号
健壮属性取值有两种可能的情况:
-
默认值
PTHREAD_MUTEX_STALLED
,这意味着持有互斥量的进程终止时不需要采取特别的动作。这种情况使用互斥量是未定义的,等待该互斥量解锁的应用程序会被有效的“拖住
”。 -
PTHREAD_MUTEX_ROBUST
导致线程调用pthread_mtex_lock获取所,该锁被另一个进程持有,终止是没有对该锁进行解锁,此时线程会阻塞,从pthread_mtex_lock
返回值为EOWNERDEAD
而不是0。有可能保护的互斥量都需要进行恢复。
使用健壮的互斥量改变了我们使用pthread_mutex_lock
的方式,因为现在必 须检查3个返回值而不是之前的两个:不需要恢复的成功、需要恢复的成功以及 失败。即使不用健壮的互斥量,也可以只检查成功或者失败。
//指明互斥量的状态在互斥量解锁之前是一致的。
#include <pthread.h> int pthread_mutex_consistent(pthread_mutex_t *mutex);
//线程未调用此函数就对互斥量解锁,其他获取该互斥量的阻塞线程就会得到错误码ENOTRECOVERABLE,这时互斥量将不再可用。
//返回值:若成功,返回0;否则,返回错误编号
类型互斥量属性控制着互斥量的锁定特性,POSIX.1定义了4种类型:
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t*restrict attr,int*restrict
type);//获取互斥量类型属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);//修改互斥量类型属性
//两个函数的返回值: 若成功,返回0;否则,返回错误编号
1.递归互斥量
PTHREAD_MUTEX_RECURSIVE
(递归互斥量)允许同一线程在互斥量解 锁之前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加 锁次数不相同的情况下,不会释放锁。所以,如果对一个递归互斥量加锁两 次,然后解锁一次,那么这个互斥量将依然处于加锁状态,对它再次解锁以前 不能释放该锁。
-
互斥量用于保护与条件变量关联的条件,在阻塞线程之前,
pthread_cond_wait
和pthread_cond_timewai
t函数释放与条件相关的互斥量,其他线程就能获取互斥量、改变条件、释放互斥量以及给条件变量 发信号。-
改变条件时必须占有互斥量,使用
递归互斥量
就不是一个好主意。如果递归互斥量被多次加锁,然后用在调用pthread_cond_wait
函数中,那么条件永远都不会得到满足,因为pthread_cond_wait
所做的解锁操作并不能释放互 斥量。- 把现有的单线程接口放到多线程环境中,递归互斥量是非常有用的,但由于现有程序兼容性的限制,不能对函数接口进行修改。这时使用递归锁可能很难处理。
-
下图使用递归互斥量
好像是在解决并发问题。若func1和func2
是函数库中现有的函数,接口不能改变,因为存在调用这两个接口的应用程序,而且应用程序不能改动。
-
为了保持接口跟原来相同,我们把互斥量嵌入到了
数据结构
中,把这个**数 据结构的地址(x)**作为参数传入。这种方案只有在为此数据结构提供分配函数 时才可行,所以应用程序并不知道数据结构的大小(假设我们在其中增加互斥 量之后必须扩大该数据结构的大小)。- 如果在最早定义数据结构时,预留了足够的可填充字段,允许把某些填充 字段替换成互斥量,这种方法也是可行的。
- 如果
func1和func2
函数都必须操作这个结构,而且可能会有一个以上的线程 同时访问该数据结构,那么 func1 和 func2 必须在操作数据以前对互斥量加锁。 - 如果
func1 必须调用func2
,这时如果互斥量不是递归类型的,那么就会出现死 锁
。 - 若能在调用 func2 之前释放互斥量,在 func2 返回后重新获取互斥量,那 么就可以避免使用递归互斥量,但其他的线程 可以在 func1 执行期间抓住互斥量的控制,修改这个数据结构。这也许是不可 接受的,当然具体的情况要取决于互斥量试图提供什么样的保护。
下图显示这种情况下使用递归互斥量的一种替代方法:
-
通过提供func2 函数的私有版本,称之为
func2_locked
函数,可以保持func1和func2函数接口不 变,而且避免使用递归互斥量。 -
要调用
func2_locked
函数,必须占有嵌入在数 据结构中的互斥量,这个数据结构的地址是作为参数传入的。 -
func2_locked
的函 数体包含func2的副本,func2现在只是获取互斥量,调用func2_locked,然后释放互斥量。
-
如果并不一定要保持库函数接口不变,就可以在每个函数中增加第二个参 数表明这个结构是否被调用者锁定。但是保持接口不变通常是更好的选择,可以避免实现过程中人为加入的东西对原有系统产生不良影 响。
- 提供加锁和不加锁版本的函数,这样的策略在简单的情况下通常是可行 的。在更加复杂的情况下,比如,库需要调用库以外的函数,而且可能会再次 回调库中的函数时,就需要依赖递归锁。
例:例子很简单,在main函数里创建2个线程,在线程1的函数fn1,加锁互斥量2次,但是只解锁一次。线程fn2就无法给互斥量加锁,导致一直阻塞在a处。为了能够让线程fn1能够先给互斥量加锁,在fn2里调用了sleep函数,让fn2先睡眠1秒,所以fn1就能够先给互斥量加锁了。去掉b处的注释,fn2就能锁定mutex了,程序就不会出现死锁状态了。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <signal.h>
pthread_mutex_t mt;
int i = 0;
void* fn1(void* agr)
{
int err;
pthread_mutex_lock(&mt);
if((err = pthread_mutex_lock(&mt)) < 0)
{
printf("%s\n", strerror(err));
exit(1);
}
++i;
printf("%d\n", i);
//pthread_mutex_unlock(&mt);//b
pthread_mutex_unlock(&mt);
}
void* fn2(void* arg)
{
sleep(1);//目的是让线程fn1先执行。
pthread_mutex_lock(&mt);//a
++i;
printf("second %d\n", i);
pthread_mutex_unlock(&mt);
}
int main()
{
pthread_t tid1, tid2;
pthread_mutexattr_t mat;
pthread_mutexattr_init(&mat);
//设置锁的类型为递归锁
pthread_mutexattr_settype(&mat, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mt, &mat);
pthread_create(&tid1, NULL, fn1, NULL);
pthread_create(&tid2, NULL, fn2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mt);
}
C++11替换的递归互斥量 std::recursive_mutex
。
#include <iostream>
#include <thread>
#include <mutex>
#include <unistd.h>
int g_resourceID = 1;
std::recursive_mutex mtx;
void ThreadFunc_2() {
mtx.lock(); //第二次上锁
g_resourceID = g_resourceID * g_resourceID ;
printf("g_resourceID = %d \n", g_resourceID );
mtx.unlock();
}
void ThreadFunc_1() {
mtx.lock();
g_resourceID = g_resourceID + 1;
ThreadFunc_2();
printf("g_resourceID