无锁编程:最简单例子
2013-06-20 21:46
197 查看
场景
假设存在这样一个情况:需要N个线程对一个全局的变量进行M次递增操作。首先想到的常常是,使用互斥量。当然在“无锁”的世界里,还有其它实现方式。话不多说,看代码:测试代码
gcc_sync_test.c#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define TEST_ROUND 20000 #define THREAD_NUM 10 #define SYNC #define LOCKLESS #ifndef LOCKLESS pthread_mutex_t mutex_lock; #endif static volatile int count = 0; void *test_func(void *arg) { int i = 0; for(i = 0; i < TEST_ROUND; i++){ #ifdef SYNC #ifdef LOCKLESS __sync_fetch_and_add(&count, 1); #else pthread_mutex_lock(&mutex_lock); count++; pthread_mutex_unlock(&mutex_lock); #endif #else count++; #endif } return NULL; } int main(int argc, const char *argv[]) { pthread_t thread_ids[THREAD_NUM]; int i = 0; #ifndef LOCKLESS pthread_mutex_init(&mutex_lock, NULL); #endif for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){ pthread_create(&thread_ids[i], NULL, test_func, NULL); } for(i = 0; i < sizeof(thread_ids)/sizeof(pthread_t); i++){ pthread_join(thread_ids[i], NULL); } printf("count=%d\r\n", count); return 0; }Makefile
CC=gcc CFLAGS= -Wall LIB= -lpthread OBJS=gcc_sync_test.o SRCS=${OBJS:%.o=%.c} TARGETS=.depend gcc_sync_test all:$(TARGETS) .depend: @$(CC) $(CFLAGS) -MM $(SRCS) > .depend -include .depend gcc_sync_test: $(OBJS) $(CC) $(CFLAGS) $^ $(LIB) -o $@ @echo $@ > .gitignore clean: rm -rf $(OBJS) $(TARGETS) .c.o: $(CC) $(CFLAGS) -c $< -o $@
测试结果
在源代码文件gcc_sync_test.c中,使用SYNC宏来控制是否启用线程间同步;在启用SYNC情况下,使用LOCKLESS宏来控制是否使用“无锁”方式,还是使用互斥量方式。选择“无锁”方式,编译、运行程序可以得到正确的结果“count=200000”,这和使用互斥量方式得到的结果一样。如果感兴趣的话,可以试试不采用任何一种同步方案(即,注释掉SYNC宏的定义),可以发现输出的结果是不正确的,道理很显然。
结果分析
那么,为什么不用phtread_mutex_lock也可以实现线程间同步?可以看到程序中使用了__sync_fetch_and_add在实现加法运算。__sync_fetch_and_add是GCC内建的原子操作,它的原理《GCC内建的原子操作》中已经做了简单的叙述。如果关注GCC是如何实现该原子操作的,可以通过生成汇编代码的方式来探究。gcc -S gcc_sync_test.c生成汇编代码gcc_sync_test.s。查看它,可以发现其中有如下代码:
jmp .L2 .L3: lock addl $1, count(%rip) addl $1, -4(%rbp) .L2: cmpl $19999, -4(%rbp)
其中,“lock addl $1, count(%rip)”中的lock既是关键所在。lock是一个指令前缀,Intel的手册上对其的解释是:
Causes the processor's LOCK# signal to be asserted during execution of the accompanying instruction (turns the instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal insures that the processor has exclusive use of any shared memory while the signal is asserted.
如果不使用原子操作__sync_fetch_and_add,直接进行count++的话,产生的汇编代码大致是这样的:
jmp .L2 .L3: movl count(%rip), %eax addl $1, %eax movl %eax, count(%rip) addl $1, -4(%rbp) .L2: cmpl $19999, -4(%rbp)显然缺了lock指令前缀。
其它
至于,为什么count变量要是volatile的,这是避免使用gcc优化选项后直接将M此循环的结果算出,影响了实例代码的显著性。读者可以自己尝试一下:去掉volatile修饰,gcc编译时使用-O2优化,不使用任何同步的情况下(不启用SYNC宏),似乎也能得到正确的结果。相关文章推荐
- Spring AOP面向切面编程一个简单例子和在配置过程中出现错误
- socket编程——一个简单的例子
- linux socket编程以及简单的tcp,udp的例子
- Spring AOP 编程最简单的例子
- 关于使用PRO*C编程的一些简单说明和例子
- Sql Server2008——存储过程编程简单例子
- socket编程---一个简单例子
- 3个学习Socket编程的简单例子:TCP Server/Client, Select
- 一个时间编程简单例子
- 网络编程简单例子
- 简单的windows服务编程框架(MSDN例子代码)
- [C++] Windows下的socket编程(这是一个简单的TCP/IP例子)
- 初识Shell Scripts编程--最最简单的shell例子
- Linux socket 编程简单例子
- 利用vc6.0进行ADO编程的简单例子
- socket编程之完成端口(附一个简单的IOCP例子)
- socket编程——一个简单的例子
- 简单的说一下:tarits技法就是一种模板元编程,起可以将本来处于运行期的事拉到编译期来做,增加了运行效率。 看以非模板元编程的例子,就是前面的那个例子:
- 阿翔编程学-struts2.0简单的例子
- 关于使用PRO*C编程的一些简单说明和例子