python cffi调用sync实现原子计数及cas

通常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.


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc