一、Linux下的互斥量:
对于某个共享资源,它是被多个线程共同拥有的,不能被某一个线程单独占有,当某个线程使用这个共享资源时其他线程不能使用,这时就需要一个互斥量来控制对共享资源的互斥访问。总而言之互斥量是为了使线程之间能够互斥的访问某个共享资源。
初始化:
互斥量的类型pthread_mutex_t mutex_, 在使用该互斥量前要对其初始化。对互斥量的初始化方法有两种:
1、 使用PTHREAD_MUTEX_INITIALIZER静态分配
mutex = PTHREAD_MUTEX_INITIALIZER
2、 使用库函数pthread_mutex_init动态分配,然后在使用pthread_mutex_destroy释放。
int pthread_mutex_init(pthread_mutex_t *mutex_,pthread_mutexattr_t * res);
int pthread_mutex_destory(pthread_mutex_t * mutex_);
返回值为0,说明函数执行成功;否则,执行失败;
加锁/解锁
intpthread_mutex_lock(pthread_mutex_t * mutex_);
intpthread_mutex_unlock(pthread_mutex_t * mutex_);
返回值为0,说明函数执行成功;否则,执行失败;
例子:
对于一个全局变量counter= 0作为共享资源,现在有两个线程thread1和thread2函数对其进行操作。其中,thread1对counter执行加1操作,thread2用于控制当counter>=0时打印出消息并且将其恢复为0。
代码:
intcounter=0;
pthread_mutex_tmutex;
void*thread1(void*)
{
while(1)
{
pthread_mutex_lock(&mutex);
++counter;
pthread_mutex_unlock(&mutex);
}
}
void*thread2(void*)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(counter>=100000000)
{
cout<<"counter is:"<<counter<<endl;
counter=0;
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
}
}
intmain()
{
pthread_mutex_init(&mutex,NULL);
pthread_tth1,th2;
pthread_create(&th1,NULL,thread1,NULL);
pthread_create(&th2,NULL,thread2,NULL);
pthread_join(th2,NULL);
pthread_join(th1,NULL);
pthread_mutex_destroy(&mutex);
return0;
}
这种情况下,由于线程2中的判断语句很多情况都不满足,但该线程同样被执行,与线程1争夺CPU资源,造成大量CPU大量时间做无用功。
二、linux下的条件变量
条件变量为:pthread_cond_t cond;
初始化
在使用该条件变量前要对其初始化。对条件变量的初始化方法有两种:
1、 使用PTHREAD_COND_INITIALIZER静态分配
cond = PTHREAD_COND_INITIALIZER
2、使用库函数pthread_cond_init动态分配,然后在使用pthread_cond_destroy释放
int pthread_cond_init(pthread_cond_t *mutex_, pthread_condattr_t* res);
int pthread_cond_destory(pthread_mutex_t * cond);
返回值为0,说明函数执行成功;否则,执行失败;
等待
函数:pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t * mute);
1、在使用pthread_cond_wait之前要对mutex加锁(pthread_mutex_lock(&mutex))。然后执行wait函数,在wait函数中,首先解锁,然后如果不满足条件,该线程进入阻塞状态,进入阻塞队列。等待cond的信号,直到条件满足后,激活并该阻塞线程,离开wait函数之前会重新锁定mutex。
2、pthread_cond_wait函数内部相当于解锁互斥量,等待条件满足信号,加锁互斥量。在其他线程中当条件满足时会使用pthread_cond_signal(&cond)发送信号激活被该条件变量阻塞的线程。
激活
当条件满足时,使用pthread_cond_signal(&cond)函数发送信号,说明条件满足了从而激活因为等待该条件cond而阻塞的线程,从阻塞队列中出对一个线程执行。当然也可以使用pthread_cond_broadcast(&cond);激活所有因为该条件而阻塞的线程。
三、互斥锁与条件变量的使用
简介
线程A中:
if(条件满足时)
{
pthread_cond_signal(&cond);//发出信号,说明条件已经满足
}
线程B中:
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);//解锁,因为等待条件而阻塞,当收到信号后激活并且加锁
pthread_mutex_unlock(&mutex);
例子代码
int counter=0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void*thread1(void*)
{
while(1)
{
pthread_mutex_lock(&mutex);
++counter;
pthread_mutex_unlock(&mutex);
if(counter>=100000000)//当条件满足时,发送信号,执行被阻塞的线程
{
pthread_cond_signal(&cond);
}
}
}
void*thread2(void*)
{
while(1)
{
pthread_mutex_lock(&mutex);
while(counter<100000000)
{
pthread_cond_wait(&cond,&mutex);
}
cout<<"counter is:"<<counter<<endl;
counter=0;
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_tth1,th2;
pthread_create(&th1,NULL,thread1,NULL);
pthread_create(&th2,NULL,thread2,NULL);
pthread_join(th2,NULL);
pthread_join(th1,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return0;
}
这样当线程2中条件不满足时,线程2处于阻塞状态,不会与线程1争夺CPU,从而提高CPU效率。
条件变量与互斥锁使用的疑惑点:
(1)关于条件变量的使用为什么要使用while循环,而不是使用if条件判断?
主要是为了避免“虚假唤醒”!
我们知道当使用pthread_cond_wait函数之前要对共享资源加锁,当条件不满足时调用pthread_cond_wait(&cond, &mutex)函数。pthread_cond_wait函数分为三个原子过程:对互斥量解锁,当前线程阻塞,当条件满足时再对互斥量加锁。
问题就出现在:当条件满足时再对互斥量加锁这个过程,因为当条件满足时当前线程不会立即获取互斥量,有可能其他线程在使用互斥量并且对共享资源进行处理,处理后又使得条件不满足。举个例子,假设我们有一个消费者线程,2个生产者线程,他们之间共享一个数据缓冲区buffer。1号生产者获取互斥量往buffer中一个元素后释放互斥量,buffer满了。此时2号生产者也获取互斥量,向往buffer中放数据,但是发现buffer满了,释放互斥量会处于阻塞状态。此时消费者获取互斥量从buffer中取一个数据,buffer中空出一个位置,并发送信号唤醒因为该条件而阻塞的线程。2号线程收到信号,首先它要申请得到互斥量,但是此时互斥量可能被1号线程使用,1号线程发现一个空位,往buffer中填充数据然后释放互斥量,buffer又满了。但是此时2号线程已经被告知buffer有一个空位,并且正准备获取互斥量向buffer中放数据。如果只是简单的用if判断的话,生产者获取互斥量后往一个满的buffer中放数据,这显然是不合理的,造成了“虚假唤醒”。所以在pthread_cond_wait中获取互斥量后要在判断一个次,条件是否满足,以避免“虚假唤醒”。
(2)为什么要调用pthread_join函数?
当某个线程调用pthread_join函数时,当前线程会以阻塞方式停下来,首先执行pthread_join函数里的线程。pthread_join函数里的线程指向完后再继续执行当前线程。