<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>apt-blog.net   无证程序员的PT桑 &#187; Cython</title>
	<atom:link href="http://apt-blog.net/tag/cython/feed" rel="self" type="application/rss+xml" />
	<link>http://apt-blog.net</link>
	<description>潜逃中。</description>
	<lastBuildDate>Fri, 18 May 2012 11:25:05 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Python, C-Python, Cython代码与GIL的交互</title>
		<link>http://apt-blog.net/python_cpython_cython_and_gil</link>
		<comments>http://apt-blog.net/python_cpython_cython_and_gil#comments</comments>
		<pubDate>Wed, 06 Jan 2010 17:08:47 +0000</pubDate>
		<dc:creator>PT</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[CPython]]></category>
		<category><![CDATA[Cython]]></category>
		<category><![CDATA[GIL]]></category>
		<category><![CDATA[Global Interpreter Lock]]></category>
		<category><![CDATA[mutithreading]]></category>
		<category><![CDATA[thread]]></category>
		<guid isPermaLink="false">http://apt-blog.net/python_cpython_cython_and_gil</guid>
		<description><![CDATA[这篇笔记相对Python来说，有点底层，先来解释几个名词： C-Python: 或者CPython，指C实现的Python虚拟机的基础API。最通用的Python就是是基于C实现的，它的底层API称为C-Python API，所有Python代码的最终变成这些API以及数据结构的调用，才有了Python世界的精彩； Cython：准确说Cython是单独的一门语言，专门用来写在Python里面import用的扩展库。实际上Cython的语法基本上跟Python一致，而Cython有专门的“编译器”先将Cython代码转变成C（自动加入了一大堆的C-Python API），然后使用C编译器编译出最终的Python可调用的模块。 GIL：Global Interpreter Lock，是Python虚拟机的多线程机制的核心机制，翻译为：全局解释器锁。其实Python线程是操作系统级别的线程，在不同平台有不同的底层实现（如win下就用win32_thread, posix下就用pthread等），Python解释器为了使所有对象的操作是线程安全的，使用了一个全局锁（GIL）来同步所有的线程，所以造成“一个时刻只有一个Python线程运行”的伪线程假象。GIL是个颗粒度很大的锁，它的实现跟性能问题多年来也引起过争议，但到今天它还是经受起了考验，即使它让Python在多核平台下CPU得不到最大发挥。 GIL的作用很简单，任何一个线程除非获得锁，否则都在睡眠，而如果获得锁的线程一刻不释放锁，别的线程就永远睡眠下去。对于纯Python线程，这个问题不大，Python代码会通过解释器实时转换成微指令，而解释器给他们算着，每个线程执行了一定的指令数后就要把机会让给别的线程。这个过程中操作系统的调度作用比较微妙，不管操作系统怎么调度，即使把有锁线程挂起到后台，尝试唤醒没锁的，解释器也不给他任何执行机会，所以Python对象很安全。 所以一般来说，做纯Python的编程不需要考虑到GIL，它们是不同层面的东西，但是模块级别的C-Python、Cython等C层面的代码，跟Python虚拟机是平起平坐的，所以GIL很可能需要考虑，特别那些代码涉及IO阻塞、长时间运算、休眠等情况的时候（否则整个Python都在等这个耗时操作的返回，因为他们没获得锁，急也没办法）。 想体现这个过程，很简单，考虑下面的代码，一段纯Python和一段纯C的循环，每次print一段文字就睡眠一秒。 1 2 3 4 5 6 7 void _c_loop &#40; void &#41; &#123; while&#40;1&#41; &#123; printf&#40;&#34;Print from C loop\n&#34;&#41;; sleep&#40;1&#41;; &#125; &#125; 1 2 3 4 def _py_loop&#40;&#41;: while True: print &#34;Print from Python loop&#34; time.sleep&#40;1&#41; 先不管他们是如何揉合到同一Python进程里面，两个进程分别执行了这两个函数后，他们应该以大概相互间隔着输出文字；但实际情况是，Print from Python loop这句出现了一次之后（先启动了纯Python线程，否则它连启动的机会都没），剩下的输出全都是Print from C [...]]]></description>
			<content:encoded><![CDATA[<p>这篇笔记相对Python来说，有点底层，先来解释几个名词： </p>
<ul>
<li><code>C-Python</code>: 或者CPython，指<a href="http://docs.python.org/dev/c-api/">C实现的Python虚拟机的基础API</a>。最通用的Python就是是基于C实现的，它的底层API称为C-Python API，所有Python代码的最终变成这些API以及数据结构的调用，才有了Python世界的精彩； </li>
<li><code>Cython</code>：<a href="http://www.cython.org/">准确说Cython是单独的一门语言</a>，专门用来写在Python里面import用的扩展库。实际上Cython的语法基本上跟Python一致，而Cython有专门的“编译器”先将Cython代码转变成C（自动加入了一大堆的C-Python API），然后使用C编译器编译出最终的Python可调用的模块。</li>
<li><code>GIL</code>：<strong>Global Interpreter Lock</strong>，是Python虚拟机的多线程机制的核心机制，翻译为：全局解释器锁。其实Python线程是操作系统级别的线程，在不同平台有不同的底层实现（如win下就用win32_thread, posix下就用pthread等），Python解释器为了使所有对象的操作是线程安全的，使用了一个全局锁（GIL）来同步所有的线程，所以造成“一个时刻只有一个Python线程运行”的伪线程假象。GIL是个颗粒度很大的锁，它的实现跟性能问题多年来也引起过争议，但到今天它还是经受起了考验，即使它让Python在多核平台下CPU得不到最大发挥。</li>
</ul>
<p>GIL的作用很简单，任何一个线程除非获得锁，否则都在睡眠，而如果获得锁的线程一刻不释放锁，别的线程就永远睡眠下去。对于纯Python线程，这个问题不大，Python代码会通过解释器实时转换成微指令，而解释器给他们算着，每个线程执行了一定的指令数后就要把机会让给别的线程。这个过程中操作系统的调度作用比较微妙，不管操作系统怎么调度，即使把有锁线程挂起到后台，尝试唤醒没锁的，解释器也不给他任何执行机会，所以Python对象很安全。 </p>
<p>所以一般来说，做纯Python的编程不需要考虑到GIL，它们是不同层面的东西，但是模块级别的C-Python、Cython等C层面的代码，跟Python虚拟机是平起平坐的，所以GIL很可能需要考虑，特别那些代码涉及IO阻塞、长时间运算、休眠等情况的时候（否则整个Python都在等这个耗时操作的返回，因为他们没获得锁，急也没办法）。 </p>
<p>想体现这个过程，很简单，考虑下面的代码，一段纯Python和一段纯C的循环，每次print一段文字就睡眠一秒。</p>
<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
</pre></td><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #993333;">void</span> _c_loop <span style="color: #009900;">&#40;</span> <span style="color: #993333;">void</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">while</span><span style="color: #009900;">&#40;</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;Print from C loop<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        sleep<span style="color: #009900;">&#40;</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>
<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> _py_loop<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #008000;">True</span>:
        <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Print from Python loop&quot;</span>
        <span style="color: #dc143c;">time</span>.<span style="color: black;">sleep</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span></pre></td></tr></table></div>
<p>先不管他们是如何揉合到同一Python进程里面，两个进程分别执行了这两个函数后，他们应该以大概相互间隔着输出文字；但实际情况是，<code>Print from Python loop</code>这句出现了一次之后（先启动了纯Python线程，否则它连启动的机会都没），剩下的输出全都是<code>Print from C loop</code>，不断的输出，按<code>Ctrl + C</code>都没反应（因为响应信号的只有住线程，终止信号是发出了，说不定主线程也收到了，但是解释器不给机会），只好从另外的控制台kill了整个python进程。 显然 问题在于，运行C那段程序的进程获取了GIL，但是这是个死循环是永远都出不去的，所以GIL永远都在这个进程手里，别的进程只能睡觉。考虑到这个C程序的情况，因为循环里面只有printf跟sleep两个操作系统的调用，完全威胁不到Python对象的安全，所以GIL完全没必要插手进来。解决办法有两种：</p>
<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #993333;">void</span> _c_loop_1 <span style="color: #009900;">&#40;</span> <span style="color: #993333;">void</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    Py_BEGIN_ALLOW_THREADS
    <span style="color: #b1b100;">while</span><span style="color: #009900;">&#40;</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;Print from C loop<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        sleep<span style="color: #009900;">&#40;</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    Py_END_ALLOW_THREADS
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #993333;">void</span> _c_loop_2 <span style="color: #009900;">&#40;</span> <span style="color: #993333;">void</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">while</span><span style="color: #009900;">&#40;</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        Py_BEGIN_ALLOW_THREADS
        <span style="color: #000066;">printf</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;Print from C loop<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        sleep<span style="color: #009900;">&#40;</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        Py_END_ALLOW_THREADS
    <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>
<p><a href="http://docs.python.org/dev/c-api/init.html#Py_BEGIN_ALLOW_THREADS">Py_BEGIN_ALLOW_THREADS、Py_END_ALLOW_THREADS</a>是定义在Python的C-API头文件里面的宏，需要给头文件包含上<code>#include</code>。</p>
<p>两个解决办法看起来差不多，效果也差不多，但意义不大一样。 </p>
<p><code>_c_loop_1</code>是在进入永远都不会停的循环之前，把GIL交出去（还有保存自己的线程状态，但现在的例子这个没有意义），然后自己继续运行，这时候<code>_c_loop_1</code>是跟python虚拟机是互不相关的线程了，井水不犯河水，解释器的GIL爱给谁给谁。这时候“Python只能有一个线程运行”的传说就被破除迷信了。</p>
<p><code>_c_loop_2</code>则是每次进行<code>printf</code>/<code>sleep</code>的调用前交出GIL，之后又申请要回来，然后循环回去马上又交出去（=。=）…… 这个例子来说，当然是前者有效率了（=。=反正都是无用功，何来效率），只是为了在复杂的情况（C代码会影响Python对象，调用Python的函数等）下灵活运用。<a href="http://docs.python.org/dev/c-api/init.html#Py_BEGIN_ALLOW_THREADS">Py_BEGIN_ALLOW_THREADS、Py_END_ALLOW_THREADS</a>其实只是两句语句的宏包含，Python手册里面有详细说明，甚至直接打开Python的include就可以看到。</p>
<p>其中BEGIN那句调用了<code>PyEval_SaveThread()</code>，而打开Python源码，<code>PyEval_SaveThread</code>函数里面毅然有<code>PyThread_release_lock()</code>的调用。 上面这些，在<a href="http://www.douban.com/subject/3117898/">《Python源码剖析——深度探索动态语言核心技术》</a>一书，<a href="http://book.51cto.com/art/200807/82538.htm">第15章Python多线程机制 15.4.2阻塞调度</a>一节讲解得比较详细，书里面是直接拿出<code>time.sleep</code>/<code>raw_input</code>这些Python本身的代码来举例和分析。当然咯，本文加入了很多笔者自己的发散性理解。 </p>
<p>Cython在旁边说，怎么还没到我出场。恩，马上就来。 </p>
<p>Cython其实是很纠结的东西，因为他用的是Python一样的语法，但经常不得不时时刻刻提醒自己，有时候是在写C，有时候在写Python（=。=）。C是强类型的，Python是弱类型的，所以写Cython的时候有时候是强类型的，有时候是弱类型的，不小心把漏写参数类型，不过还好Cython的编译器是有提示的，当熟悉了规则后，其实就是<strong>用Python写C啊</strong>……通过Cython，不仅可以在Python调用C，还可以在C调用Python……Python的上天入地就靠他了。 </p>
<p>Cython代码最终都是会编译成C代码，上面例子那段py_loop，改个名字叫cy_loop，直接放入Cython里面编译即可，他完全会变成C，然后变得像C一样霸道，执行后拿了GIL永远不还回去。 显然，只有纯Python的代码需要在执行时候经过解释器解释的，才会“自动的”交回GIL，而Cython是编译成C的代码，执行不经过解释器，如果他不交回GIL，就拿它没办法的了。 Cython是留有GIL的交互接口，在<a href="http://docs.cython.org/src/userguide/external_C_code.html#acquiring-and-releasing-the-gil">Cython手册里面加起来才半页的说明</a>，语焉不详地介绍了Cython下跟GIL的交互方法，如果没理解上面GIL的工作方式，把这两页翻烂了都弄不透。像刚才的例子，cy_loop可以写成这样：</p>
<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> cy_loop<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">with</span> nogil:
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #008000;">True</span>:
            printf<span style="color: black;">&#40;</span><span style="color: #483d8b;">&quot;Print from Cython loop<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: black;">&#41;</span>
            sleep<span style="color: black;">&#40;</span><span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span></pre></td></tr></table></div>
<p>就是用<code>with nogil</code>块把循环围起来，对，就加一行。如果打开Cython转换而成的C代码，发现Cython用<code>Py_UNBLOCK_THREADS</code>和<code>Py_BLOCK_THREADS</code>把这段循环括起来了，查看Python C-API，<code>Py_UNBLOCK_THREADS</code>跟<a href="http://docs.python.org/dev/c-api/init.html#Py_BEGIN_ALLOW_THREADS">Py_BEGIN_ALLOW_THREADS</a>其实是一样的，只差一个花括号。不过Cython还会盯着你在nogil块里面的代码，里面不能引用Python对象，不能调用Python的函数，要画清界线！啊我只是调用个<code>printf</code>跟<code>sleep</code>，人格保证这是C库的函数！可是这样Cython还是不让过，说在nogil里面不能调用需要gil的函数，恩，<code>printf</code>跟<code>sleep</code>是通过cdef预定义的，Cython可不知道这些函数是哪里的，就默认它们需要gil了。所以要在cdef引入的时候，在后面加上nogil的声明：</p>
<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">cdef extern from *:
    unsigned int sleep(unsigned int seconds) nogil
    int printf(char *format, ...) nogil</pre></div></div>
<p>嗯，就这样，Cython编译器终于高兴了。 </p>
<p>Cython里面关于GIL的另外一个接口是：</p>
<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">cdef void callback_func(void) with gil:
    ...</pre></div></div>
<p>Cython会生成<code>PyGILState_Ensure()</code>的调用，来保证这个函数在线程获得锁的时候才运行。这个情况一般是用于C的回调函数，因为回调运行是不知道什么时候的，如果这个函数里面有对Python的引用，就需要保证获得了GIL才操作。又或者你在C代码里面又生成了新的线程，而且也会引用Python的东西（纠结……）。 本文<a href="http://ptcoding.googlecode.com/files/cython_gil.tar.gz">例子代码包可这里下载</a>，或<a href="http://code.google.com/p/ptcoding/source/browse/#git%2Fcython_gil">Google Code在线查看</a>；例子里面是分别是Python、Cython、C三个循环，因为有Cython的GIL接口，C代码里面就没有使用API的宏了（注释掉）。例子里面添加了全局变量end，主线程进入pause后按<code>Ctrl + C</code>触发使其为真，然后三个线程会相继退出。例子在linux下编译，如在win下编译可能要修改下C代码里面的sleep，以及signal.pause()。 </p>
<p>例子代码运行效果： </p>
<p><img alt="例子代码运行效果" src="http://apt-blog.net/wp-content/uploads/2010/01/cython_loop.png" /></p>
]]></content:encoded>
			<wfw:commentRss>http://apt-blog.net/python_cpython_cython_and_gil/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

