多线程访问资源不加锁易出错的证明以及解决方法

多线程访问资源不同步易出错的证明以及解决方法

       线程同步简单的说就是当多个线程共享相同的内存时,当某个线程可以修改变量,而其他线程也可以读取或修改这个变量的时候,就需要对这些线程进行同步,以确保他们在访问变量的存储内容时不会访问到无效的数值。

       我们来看一个对于多线程访问共享变量造成竞争的一个例子,假设增量操作分为以下三个步骤:
(1)从内存单元读入寄存器
(2)在寄存器中进行变量值的增加
(3)把新的值写回内存单元
那么当两个 非同步线程对同一个变量做增操作时就可能出现下面这种情况
多线程访问资源不加锁易出错的证明以及解决方法_第1张图片

       也就是说如果两个人线程试图在几乎在同一时刻对同一个变量做增操作而不进行同步的话,结果就可能出现不一致,变量的值可能比原来增加了一也可能比原来的值增加了2,具体是1还是2,取决于第二个线程执行第一步的时间。如果是在第一个线程的第一步和第三步之间那么第二个线程读到的数值就和第一个线程读到的一样(因为第一个线程还没有回写更新后的数据),那么变量的值只增加了1,否则变量增加的是2。

       那么为何会出现这个问题呢,如果对共享变量的修改是原子操作那么就不存在这种竞争,但是现代的计算机存储访问周期通常需要多个总线周期,因此会出现以上错误。

下面使用实例加以验证:

头文件apue.h

#include
#include

#include
//about thread
#include

#include

#include
//about memset

源文件14.2.c

/*************************************************************************
    > File Name: 14.2.c
    > Created Time: 2016年11月25日 星期五 16时38分31秒
 ************************************************************************/
/*
 * 测试不用互斥量i是否会出错,线程1-4修改i的时候,如果没有出错,那么i1+i2+i3+i4=i应该总是成立的,此处用线程5来检查是否出错,出错就对变量i加锁,并输出i1,i2,i3,i4,i5,发现大于100 0000时候不加锁一般会出现错误
 * */
#include"apue.h"
int i1,i2,i3,i4,i;

void *thread_fun1(void *arg)
{
	while(1)
	{
		i1++;
		i++;
	}
	return (void *)0;
}
void *thread_fun2(void *arg)
{
	while(1)
	{
		i2++;
		i++;
	}
	return (void *)0;
}
void *thread_fun3(void *arg)
{
	while(1)
	{
		i3++;
		i++;
	}
	return (void *)0;
}
void *thread_fun4(void *arg)
{
	while(1)
	{
		i4++;
		i++;
	}
	return (void *)0;
}
void *thread_fun5(void *arg)
{
	while(1)
	{
		if((i1+i2+i3+i4)!=i)
		{
			printf("it's wrong:\ni1=%d\ni2=%d\ni3=%d\ni4=%d\ni=%d\n",i1,i2,i3,i4,i);
			break;
		}
	}
	return (void *)0;
}
int main()
{
	pthread_t tid1,tid2,tid3,tid4,tid5;
	int err;
	i = 0;
	i1 = 0;
	i2 = 0;
	i3 = 0;
	i4 = 0;
	//创建新线程
	err= pthread_create(&tid1,NULL,thread_fun1,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid2,NULL,thread_fun2,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid3,NULL,thread_fun3,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid4,NULL,thread_fun4,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid5,NULL,thread_fun5,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	//等待新线程运行结束
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_join(tid3,NULL);
	pthread_join(tid4,NULL);
	pthread_join(tid5,NULL);
	printf("it's over:\ni1=%d\ni2=%d\ni3=%d\ni4=%d\ni=%d",i1,i2,i3,i4,i);
	return 0;
}

编译运行结果如下:

多线程访问资源不加锁易出错的证明以及解决方法_第2张图片

很明显i1+i2+i3+i4!=i(3221417!=3221415),验证成功了!



       那么如何解决这个矛盾呢--当然是加锁同步了,我们可以把互斥量想成一把锁。在访问共享资源前 对互斥量进行加锁,在访问完后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再对该互斥量加锁的线程都会被阻塞直到当前持有锁的线程释放锁。
c语言中互斥量提供下列接口:
int pthread_mutex_init(pthread_mutex_t *restrict mutex;    //初始化互斥量
   const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);  //回收非配给该互斥量的资源
      
int pthread_mutex_lock(pthread_mutex_t *mutex);    //对互斥量加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //释放加在互斥量上的锁
       如果不希望线程被阻塞可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果此时互斥量未被加锁,那么thread_mutex_trylock将锁住互斥量,否则pthread_mutex_trylock就会失败,不能锁住互斥量,返回EBUSY。

       我们对上面4个线程竞争访问共享变量进行加锁,发现输出就正常,实例如下:

头文件如上apue.h

源文件14.3.c

/*************************************************************************
    > File Name: 14.3.c
    > Created Time: 2016年11月25日 星期五 16时38分31秒
 ************************************************************************/
/*
 * 测试用互斥量i是不会出错的,线程1-4修改i的时候,因为加锁了,所以i1+i2+i3+i4=i应该总是成立的,此处用线程5来输出结果,不会出现错误
 * */
#include"apue.h"
int i1,i2,i3,i4,i;
//加锁,对资源变量访问进行加锁,防止产生错乱
pthread_mutex_t mutex;
void *thread_fun1(void *arg)
{
	while(1)
	{
		//加锁,对整个资源变量访问进行加锁,防止产生错乱
		pthread_mutex_lock(&mutex);
		i1++;
		i++;
		//访问变量结束解锁,只有这样别人才能访问
		pthread_mutex_unlock(&mutex);
	}
	return (void *)0;
}
void *thread_fun2(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		i2++;
		i++;
		pthread_mutex_unlock(&mutex);
	}
	return (void *)0;
}
void *thread_fun3(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		i3++;
		i++;
		pthread_mutex_unlock(&mutex);
	}
	return (void *)0;
}
void *thread_fun4(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		i4++;
		i++;
		pthread_mutex_unlock(&mutex);
	}
	return (void *)0;
}
void *thread_fun5(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		if((i1+i2+i3+i4)!=i)
		{
			printf("it's wrong:\ni1=%d\ni2=%d\ni3=%d\ni4=%d\ni=%d\n",i1,i2,i3,i4,i);
		}		
		printf("it's true:\ni1=%d\ni2=%d\ni3=%d\ni4=%d\ni1+i2+i3+i4=%d\ni=%d\n",i1,i2,i3,i4,i1+i2+i3+i4,i);
		pthread_mutex_unlock(&mutex);
		sleep(5);
	}
	return (void *)0;
}
int main()
{
	pthread_t tid1,tid2,tid3,tid4,tid5;
	int err;
	i = 0;
	i1 = 0;
	i2 = 0;
	i3 = 0;
	i4 = 0;
	//对互斥量进行初始化,只有初始化的互斥量才能使用
	err = pthread_mutex_init(&mutex,NULL);
	if(err!=0)
	{
		printf("init mutex failed\n");
		return;
	}
	//窗建新线程
	err= pthread_create(&tid1,NULL,thread_fun1,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid2,NULL,thread_fun2,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid3,NULL,thread_fun3,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid4,NULL,thread_fun4,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	err= pthread_create(&tid5,NULL,thread_fun5,NULL);
	if(err!=0)
	{
		printf("create new thread failed\n");
		return;
	}
	//等待新线程运行结束
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_join(tid3,NULL);
	pthread_join(tid4,NULL);
	pthread_join(tid5,NULL);
	printf("it's over:\ni1=%d\ni2=%d\ni3=%d\ni4=%d\ni=%d",i1,i2,i3,i4,i);
	return 0;
}

编译运行结果如下:

多线程访问资源不加锁易出错的证明以及解决方法_第3张图片

很明显加锁后数据就同步了,没有出现任何错误!

以上是对多线程同步的一点点理解,以及一个验证、一个解决的实例,贴在此处仅供参考!



你可能感兴趣的:(Linux,C)