多线程编程之互斥量
最近在看POSIX多线程编程,想把自己看完之后还记得的只言片语记录一下,加深记忆。
如果多个线程对同一个内存区域进行读写操作,可以使用互斥锁保证某个线程对该内存的操作具有原子性。
使用多线程是为了使程序的运行达到并发甚至并行的目的,互斥锁的使用使多个线程在临界区只能一个一个的运行。
临界区是指操作全局内存区域的那一块代码段。
互斥量的初始化:
pthread_t mutex = PTHREAD_MUTEX_INITIALIZER; 静态初始化
pthread_mutex_init();动态初始化,使用动态初始化就需要配合pthread_mutex_destroy()释放互斥量
互斥量的三个函数:
pthread_mutex_lock(pthread_t *mutex);
pthread_mutex_trylock(pthread_t *mutex);
pthread_mutex_unlock(pthread_t *mutex);
上面的几个函数是凭印象写出来的,用的时候还需要自己在查一下。
谈到互斥量就不可避免的要说到死锁,死锁产生的原因有很多,下面这种情况比较典型,A进程给1号资源加了锁,此时需要
2号资源,而B进程给2号资源加了锁,此时需要1号资源。这种情况下A等待B解锁2号资源,B等待A解锁一号资源,A和B发生了
死锁。
要产生上面那种情况,首先需要多个共享资源,其次进程对共享资源的使用是互斥的,即同一时间只能有一个进程占有该资源(
互斥条件),一个进程已经占有某个资源,保持对该进程占有的情况下,又去获取其他资源(请求和保持条件),进程已获得的资源,在
未使用完之前,不可被剥夺,只能有自己使用完释放(不可剥夺条件)。
避免死锁方法,要避免上述情况的死锁,可以采用顺序加锁法和回退算法。
顺序加锁法,法如其名,对于共享资源1,2,3,线程A和B按照统一的顺序对他们加锁,即A是从1到3加锁,B也从1到三加锁,这样可以
避免A和B之间形成资源的环路等待。
回退算法:A和B各自选一个方向对共享资源组进行加锁(方向不必相同),A首先对第一个资源进行加锁( pthread_mutex_lock() ),加锁
成功后对其他的资源一次执行试加锁( pthread_mutex_trylock() ),如果试加锁的资源已经被其他进程加锁,A进程将自己已经所占有的资源全
部解锁。这样的话需要A所占有的资源的其他进程就可以加锁成功,避免了死锁。
在回退算法里面之所以对第一个资源进行直接加锁而不是试加锁,我认为是因为各进程都是按顺序进行加锁(别管顺序是否统一),如果第一
个锁就加锁不成功,那基本上是产生不了死锁了。也没必要一次解除它锁占有的资源(毕竟在加锁第一个资源之前该线程是没有占有资源的)。
下面的程序可以来验证回退算法,backoff=0,两线程反方向加锁,产生死锁,backoff=1,两线程反方向加锁,使用了回退算法,不会发生死
锁。
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
pthread_mutex_t mutex[3] = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER
};
void *thread_forward(void *);
void *thread_backward(void *arg);
int backoff = 1;
int main(int argc,char *argv[])
{
pthread_t forward,backward;
if (argc < 2)
{
printf("need two prm\n");
return 0;
}
backoff = atoi(argv[1]);
pthread_create(&forward,NULL,thread_forward,NULL);
pthread_create(&backward,NULL,thread_backward,NULL);
pthread_join(forward,NULL);
pthread_join(backward,NULL);
return 0;
}
void *thread_forward(void *arg)
{
int i = 0;
int status = 0;
for (; i < 3; i++)
{
if (0 == i)
{
status = pthread_mutex_lock(&mutex[i]);
if (status != 0)
{
printf("mutex_lock:%s",strerror(status));
return NULL;
}
continue;
}
if (!backoff)
{
status = pthread_mutex_lock(&mutex[i]);
if (status != 0)
{
printf("mutex_lock:%s",strerror(status));
return NULL;
}
}
else
{
status = pthread_mutex_trylock(&mutex[i]);
if (status == EBUSY)
{
printf("locked at:%d\n",i);
for (; i >= 0; i--)
{
status = pthread_mutex_unlock(&mutex[i]);
if (status != 0)
{
printf("mutex_unlock:%s",strerror(status));
return NULL;
}
}
}
}
usleep(1); //不加sleep()函数可能看不出死锁的效果,因为一个线程可能在另一个线程执行之前就完成了所有互斥量的加锁解锁工作
}
for (i = 2; i >= 0; i--)
{
status = pthread_mutex_unlock(&mutex[i]);
if (status != 0)
{
printf("mutex_unlock:%s",strerror(status));
return NULL;
}
}
}
void *thread_backward(void *arg)
{
int i = 2;
int status = 0;
for (; i >= 0; i--)
{
if (2 == i)
{
status = pthread_mutex_lock(&mutex[i]);
if (status != 0)
{
printf("mutex_lock,backward:%s",strerror(status));
return NULL;
}
continue;
}
if (!backoff)
{
status = pthread_mutex_lock(&mutex[i]);
if (status != 0)
{
printf("mutex_lock:%s",strerror(status));
return NULL;
}
}
else
{
status = pthread_mutex_trylock(&mutex[i]);
if (status == EBUSY)
{
printf("[bakckward],locked at:%d\n",i);
for (; i < 3; i++)
{
status = pthread_mutex_unlock(&mutex[i]);
if (status != 0)
{
printf("mutex_unlock:%s",strerror(status));
return NULL;
}
}
}
}
usleep(1);
}
for (; i < 3; i++)
{
status = pthread_mutex_unlock(&mutex[i]);
if (status != 0)
{
printf("mutex_unlock:%s",strerror(status));
return NULL;
}
}
}