并发编程中用到的几种常见锁

没有加锁而造成的数据竞争

任务:使用10个线程,同时对一个count加100000;最后我们期望的结果是100000;

实验代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define THREAD_COUNT	10

//线程回调函数
void *thread_callback(void *args)
{
	int *pcount = (int *)args;
	int i = 0;

	//依次增加100000
	while(i ++ < 100000)
	{
		(*pcount)++; //无锁	
		usleep(1); //睡眠1微秒
	}

}

int main()
{
	clock_t start, finish;
	void *thread_result;
	pthread_t threadid[THREAD_COUNT] = {0};

	start = clock();

	int i = 0;
	int count = 0;
	for(i = 0; i < THREAD_COUNT; ++i)
	{
		//创建线程
		pthread_create(&threadid[i], NULL, thread_callback, &count);

	}
	
	for(i = 0; i < THREAD_COUNT; ++i) {
		pthread_join(threadid[i], &thread_result);
	}
	finish = clock();
	printf("count: %d\n", count);
	printf("NoLock : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);


	return 0;
}

实验结果

在这里插入图片描述

观察发现,count并没有加到1000000,原因如下:

(*pcount)++可以分解为3条汇编指令

mov [count], eax;
inc eax;
mov eax, [count];

在并发环境下,会出现如下的执行顺序

在这里插入图片描述

如:count = 50, 线程1先执行 mov [count], eax;即eax = 50

然后转到线程2执行三条汇编指令,执行后count为51;

此时再转回去线程1执行剩余两条语句,由于前面eax = 50,经过inc eax之后eax变为51,再经过mov eax, [count],count 变为51,发现两次执行(*pcount)++之后 *pcount只自增了1。

*解决办法:在进行 (pcount)++时进行加锁

互斥锁(mutex)

实现代码如下

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define THREAD_COUNT	10

pthread_mutex_t mutex;
//线程回调函数
void *thread_callback(void *args)
{
	int *pcount = (int *)args;
	int i = 0;

	//依次增加100000
	while(i ++ < 100000)
	{
		pthread_mutex_lock(&mutex); //加互斥锁
		(*pcount)++;
		pthread_mutex_unlock(&mutex); //解锁
		usleep(1); //睡眠1微秒
	}

}

int main()
{
	clock_t start, finish;
	void *thread_result;
	pthread_t threadid[THREAD_COUNT] = {0};

	start = clock();
    pthread_mutex_init(&mutex, NULL);
	int i = 0;
	int count = 0;
	for(i = 0; i < THREAD_COUNT; ++i)
	{
		//创建线程
		pthread_create(&threadid[i], NULL, thread_callback, &count);

	}
	
	for(i = 0; i < THREAD_COUNT; ++i) {
		pthread_join(threadid[i], &thread_result);
	}
	finish = clock();
	printf("count: %d\n", count);
	printf("mutex : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);
    
	pthread_mutex_destroy(&mutex);
	return 0;
}

实验结果

在这里插入图片描述

可以看出,count最终加到了1000000,解决了数据竞争的问题,但是互斥锁会引起线程切换,适合于锁的内容比较多的场景使用,比如线程安全的rbtree添加节点。

自旋锁(spinlock)

自旋锁不会引起线程切换,不会让出CPU,一直在原地空转。适合于锁的内容较少的情况下使用。

代码如下

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define THREAD_COUNT	10

pthread_spinlock_t spinlock;
//线程回调函数
void *thread_callback(void *args)
{
	int *pcount = (int *)args;
	int i = 0;

	//依次增加100000
	while(i ++ < 100000)
	{
		pthread_spin_lock(&spinlock);
		(*pcount)++;
		pthread_spin_unlock(&spinlock);
		//usleep(1); //睡眠1微秒
	}

}

int main()
{
	clock_t start, finish;
	void *thread_result;
	pthread_t threadid[THREAD_COUNT] = {0};

	start = clock();
    pthread_spin_init(&mutex, NULL);
	int i = 0;
	int count = 0;
	for(i = 0; i < THREAD_COUNT; ++i)
	{
		//创建线程
		pthread_create(&threadid[i], NULL, thread_callback, &count);

	}
	
	for(i = 0; i < THREAD_COUNT; ++i) {
		pthread_join(threadid[i], &thread_result);
	}
	finish = clock();
	printf("count: %d\n", count);
	printf("mutex : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);
    
	pthread_spin_destroy(&mutex);
	return 0;
}

实验结果

在这里插入图片描述

可以看出也能解决数据竞争问题,但是时间效率低。

原子操作

原子操作就是通过汇编实现单条CPU指令实现(*pcount++)操作

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

#define THREAD_COUNT	10

//将三条汇编合成一个原子操作
int inc(int *value, int add)
{
	int old;
	__asm__ volatile(
		"lock; xaddl %2, %1;" //%1 = %2 + %1
		: "=a" (old)
		: "m" (*value), "a"(add) //%1为 *value, %2为add
		: "cc", "memory"
	);
	return old;

}
//线程回调函数
void *thread_callback(void *args)
{
	int *pcount = (int *)args;
	int i = 0;

	//依次增加100000
	while(i ++ < 100000)
	{
		//封装成原子操作
		inc(pcount, 1); 
	}

}

int main()
{
	clock_t start, finish;
	void *thread_result;
	pthread_t threadid[THREAD_COUNT] = {0};

	start = clock();

	int i = 0;
	int count = 0;
	for(i = 0; i < THREAD_COUNT; ++i)
	{
		//创建线程
		pthread_create(&threadid[i], NULL, thread_callback, &count);

	}
	
	for(i = 0; i < THREAD_COUNT; ++i) {
		pthread_join(threadid[i], &thread_result);
	}
	finish = clock();
	printf("count: %d\n", count);
	printf("atomic : %f\n", (double)(finish - start) / CLOCKS_PER_SEC);
	

	return 0;
}

实验结果

在这里插入图片描述

可以看出原子操作的时间开销相比于互斥锁以及自旋锁是最低的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值