1.问题
单线程执行任务的场景通常是简单不易出错的,但是多线程执行任务时,由于线程对于内存空间的共享特性,在并发执行时却会产生一些意料之外的错误。如下列程序:
#include "unpthread.h"
#define NLOOP 10
int counter; /* incremented by threads */
void *doit(void *);
int
main(int argc, char **argv)
{
pthread_t tidA, tidB;
Pthread_create(&tidA, NULL, &doit, NULL);
Pthread_create(&tidB, NULL, &doit, NULL);
/* 4wait for both threads to terminate */
Pthread_join(tidA, NULL);
Pthread_join(tidB, NULL);
exit(0);
}
void *
doit(void *vptr)
{
int i, val;
/*
* Each thread fetches, prints, and increments the counter NLOOP times.
* The value of the counter should increase monotonically.
*/
for (i = 0; i < NLOOP; i++) {
val = counter;
printf("%ld: %d\n", pthread_self(), val + 1);
counter = val + 1;
}
return(NULL);
}
该程序创建了两个线程,皆执行函数doit,doit函数是一个对全局变量counter的累加器。执行上述代码我们希望出现的结果是:线程A、B各对counter累加10次,counter的值最终应为20,但是实际却出现了以下结果:
显然,这样的结果是不对的,假设xx60为线程A,xx56为线程B,A在执行完10步之后,B本应从counter=10开始累加,实际却从counter=4开始累加了。首先要知道线程在执行这个函数时的过程是:从内存中将counter的值取出赋值给局部变量val——对val累加——把val赋回给counter,完整的完成这样的步骤才能确保counter的值被成功累加了。而出现上述结果的原因就是线程B在线程A把counter=4放回内存后就把counter的值读到了自己的val中,所以当A结束循环后B就从4开始累加了。
该问题的本质错误是对共享资源的修改缺乏控制,如果线程A在修改counter的时候,线程B就不应该对counter进行读入val的操作。互斥锁被提出来用于解决这类线程同步问题。
2.互斥锁
互斥锁是类型为pthread_mutex_t的变量,称为互斥量,可通过下列函数进行“上锁”和“解锁”的操作:
int pthread_mutex_lock(pthread_mutex_t *mptr);//上锁
int pthread_mutex_unlock(pthread_mutex_t *mptr);//解锁
mptr是互斥量
如果试图对一个已经上锁的互斥量进行上锁,则线程会阻塞,直到该互斥量被解锁。
那么互斥量应该在什么时候上锁?在什么时候解锁呢?通常来说,上锁应该在临界区之前,解锁在临界区之后。所谓临界区就是对共享资源进行操作的一连串代码,比如上述doit函数中for循环里的内容都是临界区,临界区之内同一时间只可以有一个线程在执行。
对上述代码加上互斥锁后:
#include "unpthread.h"
#define NLOOP 10
int counter; /* incremented by threads */
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void *doit(void *);
int
main(int argc, char **argv)
{
pthread_t tidA, tidB;
Pthread_create(&tidA, NULL, &doit, NULL);
Pthread_create(&tidB, NULL, &doit, NULL);
/* 4wait for both threads to terminate */
Pthread_join(tidA, NULL);
Pthread_join(tidB, NULL);
exit(0);
}
void *
doit(void *vptr)
{
int i, val;
/*
* Each thread fetches, prints, and increments the counter NLOOP times.
* The value of the counter should increase monotonically.
*/
for (i = 0; i < NLOOP; i++) {
Pthread_mutex_lock(&counter_mutex);
val = counter;
printf("%ld: %d\n", pthread_self(), val + 1);
counter = val + 1;
Pthread_mutex_unlock(&counter_mutex);
}
return(NULL);
}
于是就可以获得正确结果:
注意:如果对互斥量的值是静态分配的,那么必须在声明时初始化为PTHREAD_MUTEX_INITIALIZER。
PTHREAD_MUTEX_INITIALIZER在某些系统中被定义为0,因此忽略这个初始化也是可以的,因为静态分配的变量会自动初始化为0。但对于一些将其定义为非0的系统这样的忽略是不可被接受的,因此还是遵循规范的初始化操作比较好。
参考:《UNIX网络编程(第三版):卷I》