python cmemcache是不是线程安全的
答案是,我不确定,但是我更倾向于认为它是线程不安全的
原因在于
1、网上任何讨论,都从未提及过cmemcache所用的libmemcache是线程安全的,也没有提及过cmemcache实现了线程安全,而libmemcached从一开始就强调自己是线程安全的
2、我个人不太熟悉C代码,所以看libmemcache的代码也没有办法判断出来,但是我看到cmemcache的c接口代码在针对socket操作的代码前后使用了Py_BEGIN_ALLOW_THREADS;Py_END_ALLOW_THREADS;进行包围,而这两个宏的作用,是解除GIL,让多线程可以进入相应的代码。天啊,本来我们还期望对socket操作进行加锁,现在竟然还竟然解除GIL。本来因为GIL的存在,可能还有些希望,现在cmemcache为了让socket操作不要阻塞,反而令到自己是线程不安全的。(如有理解错误请指正)
3、python一直习惯的部署模型是单线程多进程,所以线程安全对很多类库来说,并不重要也不考虑。但是现在越来越多的python程序部署使用了多线程的模型(又或者多线程多进程),线程安全问题也凸显出来
综上所属,我的结论是,我不确定是否线程安全的,呵呵,但是我认为要进行线程安全的修正,至少没有坏处
修正方法很简单,直接套用python-memcached的做法(至于这种做法的优劣也在那篇文章有提及到)
import cmemcache as memcache from threading import local class Memcache(local): def __init__(self, servers): self._current = memcache.Client(servers) def current(self): return self._current
昨天稍微看了下libmemcache的源码,感觉应该算是线程安全的,当然前提是不同的线程要使用不同的connection(不同的mc和mctxt实例,用你说的这个办法就能保证),要是要能不同的线程共享connection才算线程安全,那恐怕没什么基于tcp的库能说线程安全了吧,加锁的话性能就损失很多了,也在很大程度上失去使用多线程的意义了。
你这个说法就是概念不对了,你可以认为我那个修正之后(例如叫python_cmemcache_sparkle_lib)是线程安全的,但是你不能认为python cmemcache和libmemcache是线程安全的(还要叫别人按照某个方法来使用)
至少别人用任何数量线程任何方式去调用你的库都没有问题才能叫做线程安全,当然你也可以就弄一个超大的锁然后跟别人说我是线程安全的
至于怎么实现线程安全,方法多的是,只有一个连接+一个大锁(你说的性能低的做法),线程绑定(我上面的做法),连接池重用等等
一般的c库都是提供一堆函数的,根本没有python这样从type级别定义thread-local的机制,c库根本就不知道你的调用是从哪个线程过来的,你要几个线程共用一个context c库有什么办法?硬要用thread-local变量的话似乎是可以做到,但是我很怀疑真有这么做的库,为了一个虚幻的“你怎么用都没有问题”来付出性能的代价,取决于系统,对于thread-local变量的每一次存取都是一次函数调用,甚至可能是系统调用。
比如libmemcached“从一开始就强调自己是线程安全的”,其实和libmemcache一模一样,它的文档写的清清楚楚”Without creating your own locking structures you can not share a single memcached_st.” 这个memcached_st和libmemcache的mc/mctxt没什么区别。
啊对了,BTW一下,照你的定义你这个”python_cmemcache_sparkle_lib”也不是线程安全的哦,我只要创建一个cmemcache.Client类型的全局变量,在一个线程里初始化Memcache后把这个current()赋给它,然后在所有线程里共用,恩,后果嘛,嘿嘿。
可能是Java和C的思考思路不一样的缘故,Java的SimpleDateFormat也要在javadoc里面注明这个线程不安全,请不要在全局使用,每次使用都new一个新的出来什么的。我也没想到libmemcached放句话在doc里面就能称自己是线程安全的,可能真的是习惯不一样,不过至少libmemcache和cmemcache没放这句话。至于第二个回复,有点掰过头了,不如取消线程安全这个概念吧。例如在pool+conn的情况下,我们通常会称pool是线程安全的,conn不是,你硬要说我从pool里面拿一个conn全局放着就不行了所以pool不是线程安全(而实际上你并不是多线程操作pool而是多线程操作conn,也只能证明conn不是线程安全而已)
对啊,对应于c的情况,这个让你传一个结构体实例的机制就相当于pool啊(区别在于低了一层,没有提供pool管理函数?),你只要在不同的线程中使用不同的实例就是安全的。我看过的c库基本都是这样做的,甚至大部分还会定义一个全局的实例和一套使用它的简化版函数,让你写单线程程序的时候调用起来更简单,不过现在这种做法ms不流行了,说是会鼓励线程不安全的程序设计,因为现在多线程越来越普遍了。
tcp的conn根本就不可能线程安全,除非你把多线程当成单线程来用,我原帖的意思就是说如果因为conn不线程安全就说库也不线程安全,那就没有线程安全的基于tcp的库了。既然库本身提供了让你在不同线程中使用不同conn的机制,那就足够了。