线程同步之互斥锁

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》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值