没有加锁而造成的数据竞争
任务:使用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;
}
实验结果

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



















