通常python实现原子计数是需要加锁解决的, 至于原因我们知道,像count += 1 这种操作不是原子的。一个加法操作,本质是分成三步的:
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新. http://xiaorui.cc/?p=4613
程序表面上来说:
1、获取count 2、加法拿到新值 3、把新值给count
底层上来说:
1、 从缓存取到寄存器 2、 在寄存器加1 3、 存入缓存。
往往会因为时序的因素,多个线程操作同一个全局变量,很大几率会出现跟预期值不一致的现象。最简单的处理办法就是加锁保护,这也是绝大数人的解决方案。 python的lock是基于pthread_mutex_lock实现的。看下面的代码:
# xiaorui.cc lock.acqire() counter += 1 lock.release()
但既然有锁,肯定是有锁消耗的。如何避免这类计数及cas带来的锁消耗? 我们知道gcc下提供了__sync_* 系列函数,这类函数都是原子操作的。 看下面的伪代码:
__sync_fetch_and_add( &counter, 1 );
gcc从4.1.2提供了__sync_*系列的built-in函数,用于提供加减和逻辑运算的原子操作。大多数语言的atomic原子函数都是这么实现的。
# xiaorui.cc type __sync_fetch_and_add (type *ptr, type value, ...) type __sync_fetch_and_sub (type *ptr, type value, ...) type __sync_fetch_and_or (type *ptr, type value, ...) type __sync_fetch_and_and (type *ptr, type value, ...) type __sync_fetch_and_xor (type *ptr, type value, ...) type __sync_fetch_and_nand (type *ptr, type value, ...) type __sync_add_and_fetch (type *ptr, type value, ...) type __sync_sub_and_fetch (type *ptr, type value, ...) type __sync_or_and_fetch (type *ptr, type value, ...) type __sync_and_and_fetch (type *ptr, type value, ...) type __sync_xor_and_fetch (type *ptr, type value, ...) type __sync_nand_and_fetch (type *ptr, type value, ...)
上面是原子的计数,下面是cas的实现:
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...) type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
mutex vs spinlock
Spinlock(自旋锁)和mutex作为两种互斥锁,在并行编程中都得到了广泛应用。那么,这两种锁有什么区别吗?
当一个线程对Spinlock加锁时,如果该锁被其他线程占用,那么该线程会通过一个loop不断地重试( try again and again);而使用mutex的线程没有得到锁时,会sleep,等待锁释放后,被唤醒。
因为,当临界区较短时,Spinlock因为没有上下文切换,可能性能更优;当临界区较长时,不断的spin将浪费大量的cpu资源。
Python C Module 扩展
在python下如何调用呢? 使用cffi来调用c代码… 那么为什么会选择用cffi来实现c代码扩充? 先介绍下Python扩展c代码的几种方式,cpython api,ctypes,cffi,cython。
CPython 原生 API, 通过引入 Python.h 头文件,好处在于样例多,好抄袭,但使用方法很是复杂,没有写纯c那种畅快感…
ctypes, 通常用于封装(wrap) C 程序,让纯 Python 程序调用动态链接库中的函数就可以了。在一些基准测试下,python2+ctypes 是性能最好的方式。但是应用在这里,传递变量地址显得麻烦。
Cython,Cython 是 CPython 的超集,用于简化编写 C 扩展的过程。简单是简单,但问题同上。
Cffi 是 ctypes 在 pypy 中的实现,同时也兼容 CPython。cffi 提供了在 python 使用C类库的方式,可以直接在 python 代码中编写 C 代码,同时支持链接到已有的 C 类库。
这里的选择看个人的喜好,我最后使用Cffi实现的扩展,主要是因为简单…
项目介绍
代码扔到github了,有兴趣的可以看看。 gcc的__sync_add_and_fetch 当然只是线程安全的,进程之间的内存地址都是虚拟的,当然不能复用的。 考虑有些人想在多进程下使用,就顺手写了个多进程计数方式,具体看代码。
github项目地址: https://github.com/rfyiamcool/AtomicPlus
END.