Cython进阶--用Cython封装Callback函数
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
Cython封裝Callback函數(shù)
1 說(shuō)明:
回調(diào)函數(shù),在C語(yǔ)言里是經(jīng)常要用到的,但是,在Python里封裝一個(gè)C的回調(diào)函數(shù)并沒(méi)有想象的那么簡(jiǎn)單,本文講解如何用Cython快速的封裝C里的回調(diào)函數(shù)
2 不多說(shuō),先上代碼:
callback.pyx
cdef extern from "pthread.h":ctypedef void * pthread_tctypedef structpthread_attr_t:passint pthread_create (pthread_t*__newthread, \pthread_attr_t *__attr, \void *(*__start_routine) (void *) except*, \void *__arg)int pthread_join (pthread_t__th, void **__thread_return)cdef extern from "Python.h":ctypedef enum PyGILState_STATE:passctypedef enum PyThreadState:passPyGILState_STATE PyGILState_Ensure()void PyGILState_Release(PyGILState_STATE)PyThreadState *PyEval_SaveThread()void PyEval_RestoreThread(PyThreadState *)void PyEval_InitThreads()void Py_INCREF(object obj)void Py_DECREF(object obj)def Callback(obj):if hasattr(obj,'run'):obj.run()cdef void * start (void * param) except*:cdef PyGILState_STATE statecdef object obj = <object>paramcdef void *retif not param:return <void *>0state = PyGILState_Ensure()Callback(obj)Py_DECREF(obj)PyGILState_Release(state)return <void*>1def CreateThread(obj):cdef int retcdef pthread_t tidPyEval_InitThreads()Py_INCREF(obj)ret = pthread_create(&tid, <pthread_attr_t *>0,start,<void *>obj)if ret != -1:return <int>tidelse:Py_DECREF(obj)return Nonedef JoinThread(int tid):cdef PyThreadState * statecdef void *thread_return =<void *>0cdef int retstate = PyEval_SaveThread()ret =pthread_join(<pthread_t>tid,&thread_return)PyEval_RestoreThread(state)if ret != -1:return <int>thread_returnelse:return <int>03 解釋一下:
3.1 這個(gè)例子封裝了多線程庫(kù)pthread的兩個(gè)函數(shù):
pthread_create
pthread_join
?
3.2 實(shí)際上,python基于Linux的多線程的實(shí)現(xiàn)正是基于pthread庫(kù)的
3.3 cdefextern from "pthread.h" 引用了兩個(gè)函數(shù)的定義:pthread_create和pthread_join
3.4 cdefextern from "Python.h" 引用了Python的五個(gè)庫(kù)函數(shù):
?PyEval_InitThreads
PyGILState_Ensure
PyGILState_Release
PyEval_SaveThread
PyEval_RestoreThread
3.5 一個(gè)一個(gè)來(lái)解析一下:
?
3.5.1 PyEval_InitThreads
? ? ?如果要讓Python代碼在不同的線程里調(diào)用,必須首先調(diào)用一下PyEval_InitThreads,只需調(diào)用一次即可,多次調(diào)用沒(méi)有影響,PyEval_InitThreads會(huì)判斷是否已經(jīng)作了初始化
?
3.5.2 PyGILState_Ensure和PyGILState_Release
? ? ? 這里就是Python頗具爭(zhēng)議的GIL(global interpreter lock),翻譯成中文就是全局解析鎖,出現(xiàn)這個(gè)東西的原因是Python的內(nèi)存管理不支持多線程,網(wǎng)上的資源一大堆,可以去查,python發(fā)展到現(xiàn)在,GIL一直還存在,是有其理由的,
其一:
? ? ? ? 它確實(shí)簡(jiǎn)化了Python的設(shè)計(jì),也使Python變得更穩(wěn)定,而在《The Zen of Python》中,第三條就是:Simple is better than complex,可以說(shuō)在奉行“大道至簡(jiǎn)”的設(shè)計(jì)準(zhǔn)則的Python來(lái)說(shuō),其原因就很好理解了。
其二:
? ? ? ? 在單CPU的情況下GIL造成對(duì)效率的影響微乎其微,優(yōu)化Python代碼本身的性能更為重要。
其三:
? ? ? ? 在多CPU情況下,為了做到并行處理,可以用進(jìn)程來(lái)替代線程,在Python里有一個(gè)Process類(lèi)可以做到在Python里多進(jìn)程運(yùn)行
其四:
? ? ? ? 好吧,我也認(rèn)為這個(gè)東西讓我感到不爽,它在簡(jiǎn)化設(shè)計(jì)者任務(wù)的同時(shí),也加重了使用者的負(fù)擔(dān)。但是暇不掩玉,就像人一樣,如果總叮著一個(gè)人的缺點(diǎn)不放,不但會(huì)影響自己的心情,還有可能會(huì)使自己失去與人交往的機(jī)會(huì)。
?
3.5.3 PyEval_SaveThread和PyEval_RestoreThread
?
可以說(shuō),這兩個(gè)函數(shù)是因GIL而生,由于Python的GIL只允許同時(shí)只有一個(gè)線程執(zhí)行Python代碼,這就有個(gè)問(wèn)題:如果一個(gè)調(diào)用C代碼的操作比較 耗時(shí),比I/O操作,那么其它線程就會(huì)被阻塞,更甚者假如一個(gè)Python線程在等另一個(gè)Python線程,那么就會(huì)造成死鎖:PyEval_SaveThread的作用就是暫時(shí)釋放GIL,使其它線程里的Python代碼得以有機(jī)會(huì)運(yùn)行
3.6 實(shí)現(xiàn)的原理是這樣的:
在pthread_create時(shí)將一個(gè)對(duì)象作為線程的參數(shù)傳遞下來(lái)
在start線程中,再調(diào)用對(duì)象的方法run,就達(dá)到了在Callback中調(diào)用Python代碼的目的
為了防止通過(guò)CreateThread傳遞進(jìn)來(lái)的對(duì)象obj在start還沒(méi)有運(yùn)行或者沒(méi)有運(yùn)行完就被垃圾回收,在pthread_create之前還調(diào)用了Py_INCREF將obj引用計(jì)數(shù)加一
在start里,運(yùn)行完P(guān)ython代碼后,再調(diào)用Py_DECREF將obj的引用計(jì)數(shù)減一
?
4 再來(lái)看下Python代碼是怎么使用這些接口的:
callme.py
callme.py#!/usr/bin/pythonimport callbackimport timeclass Thread:def __init__(self):self.tid= Nonedef start(self):self.tid= callback.CreateThread(self)def run(self):passdef join(self):callback.JoinThread(self.tid)class MyThread1(Thread):def run(self):for i in xrange(10):print 'outputfrom thread 1:',iclass MyThread2(Thread):def run(self):for i in xrange(10):print 'outputfrom thread 2:',it1 = MyThread1()t2 = MyThread2()t1.start()t2.start()t1.join()t2.join()基本上,這個(gè)例子就是一個(gè)Python內(nèi)部Thread實(shí)現(xiàn)的精簡(jiǎn)版
除了CreateThread時(shí)傳遞了一個(gè)對(duì)象外,其它的沒(méi)有什么好解釋的了
5 最后來(lái)看下編譯的代碼和運(yùn)行結(jié)果
Setup.py
Setup.py from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonizeext = Extension("callback", define_macros = [('MAJOR_VERSION', '1'),('MINOR_VERSION', '0')],include_dirs = ['/usr/local/include'],libraries = ['pthread'],library_dirs = ['/usr/local/lib'],sources = ["callback.pyx", ]) setup(name = 'callback',version = '1.0',description = 'This is a callback demo package',author = '',author_email = 'shi19@163.com',url = '',long_description = '',ext_modules=cythonize([ext,]), )5.1 運(yùn)行python Setup.py build_ext --inplace進(jìn)行編譯
5.2 編譯完了,調(diào)用
python callme.py
查看結(jié)果:
output from thread 1:output from thread 2:0 output from thread 2: 1 output from thread 2: 2 output from thread 2: 3 output from thread 2: 4output from thread 2: 0 5 output from thread 1: 1 output from thread 1: 2 output from thread 2: 6 output from thread 1: 3 output from thread 2: 7 output from thread 1: 4 output from thread 1: 5 output from thread 2: 8 output from thread 1: 6 output from thread 2: 9 output from thread 1: 7 output from thread 1: 8 output from thread 1: 9不要認(rèn)為是輸出有問(wèn)題,這正好說(shuō)明是在多線程的情況下運(yùn)行的。
示例代碼下載
(完)
轉(zhuǎn)載于:https://my.oschina.net/mickelfeng/blog/1477278
總結(jié)
以上是生活随笔為你收集整理的Cython进阶--用Cython封装Callback函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PgSQL · 最佳实践 · CPU满问
- 下一篇: Hibernate之HQL检索(查询)方