大概是线程之类的

这篇文章比较碎,大概我也是想到哪写到哪,我倒是希望能把想要说的表达清楚,不过也只能是泛泛而谈吧。

涉及的东西主要是线程,线程池,然后actor。不过乱入些别的什么也说不准倒是。

其实我写博客大致都是有个腹稿的,不过很遗憾这篇没有。

总而言之,能有那么一点干货就好了(望天。

GIL

其实说python线程,或者说多线程,大多数情况下都绕不开GIL。当然这里讨论的是标准的CPython。

GIL这个概念太过于深入人心,所以每当有针对python的任何争论的时候,GIL都是一个绕不过去的黑点。不过,真正的现实倒也不是这么残酷,不过大体而言,CPython多线程无法利用多核资源,是正确的。但是,python本身提供了multiprocessing模块,其实就是另开了进程,所以是可以利用多核资源的。

这部分其实中文wiki就说得很具体:GIL,更多的内容的话,有一份叫做《Understanding GIL》的pdf说得很具体了。

不展开详述,但是总之,python的多线程是真正的多线程,在大多数情况下,都很好用。

threading

threading的详细信息也不想多说了,大致AS平常会使用到的方法有这么一些。

threading.Event()。这个方法可以返回一个Event对象,提供了is_set(),set()这样的方法。不过其实这玩意就是一个高级的flag。恩,文档上也是这么说的对吧。

threading.active_count()threading.current_thread()。这两个都是看见名字就理解了的对吧。不过current_thread你们觉得会用来干嘛呢?AS的话,通常只会去用这个查看线程名字而已……

threading.enumerate(),这个会列举当前正在存活的所有进程,话说其实简单的filter也能做到同样的事就是了,嘛。

threading.Lock(),这个方法会返回一个锁对象,其实返回的就是一把简单的互斥锁(mutex)。就通常的项目而言,mutex其实已经很足够了。信号量的话,AS抄别人代码时,也会照猫画虎来一套,不过一般是不会想到去用啦……(逊透了)。

说到锁,大致使用是这样:

def A(mutex):
    mutex.acquire()
    try:
        do something
    finally:
        mutex.release()

当然您知道的,python有with语句,所以也有种写法是这样:

def A(mutex):
    with mutex:
        do something

据说第一种是java的经典方式,第二种比较pythonic。不过AS看内置电池的时候,比如队列啊什么的,第二种方式使用得意外地少,虽然AS猜应该是with语法引入较晚的缘故,不过受此影响,AS还是第一种用得比较多。写公司项目的时候,基本是第一种方法来着。

threading.Timer()。这方法应该是AS用得比较多的方法之一了,大致就是单独起一个线程定时执行这样。

由于python的线程可以因为异常而被迫退出,如果timer没有好好针对异常处理的话,而且任务比较重要的话,不好好考虑写法大概是不行的呢。AS习惯自调用写在finally里面。

接下来就是使用线程去执行什么的了。

比较简单的用法就是 threading.Thread(target=handler, args=(), kwargs={}).start()这样。

当然,更复杂一点是去继承Thread来增加行为。

这部分其实和大多数语言很类似,应该是吧,AS其他写过多线程的只有Java。

这里有一个比较常用的写法,是结合装饰器的。

def async(func):
    def wrapper(*args, **kwargs):
        _func = threading.Thread(target=func, args=args, kwargs=kwargs)
        _func.start()
        return _func
    return wrapper

名字比较唬人,但是实现再简单不过了。

AS在公司的时候,实现过一个线程池,很奇怪,工作了两天就不工作了。部分实现会在下面给出,现在基本排除了队列的问题,大体上我们给出的结论是线程工作一定的任务之后,会僵掉,虽然是active状态,但是不干活。虽然有可能是AS代码写得烂,不过即使用multiprocessing去替换也是一样。目前没找到原因,有经验的菊苣可以讨论解释一下……恩。

线程池

python没有提供原生的线程池,其实这让AS相当不解,不过AS想大概是线程池实现太简单?

其实我就是随便说说,嘛。

线程池的话,就是一个简单的单生产者多消费者模型,大概比无脑开线程跑新任务各方面会优势一点点?然后生产者就是主线程,消费者就是池中的线程。

大致如此,下面的简单实现看看就好了,写这篇文章的时候随手写的,有可能不能跑,orz……不过应该很容易懂就是了。

import Queue as _Queue
import threading as _threading
import logging

def Pool(object):
    def __init__(self, limit=5):
        self._limit = limit
        self._queue = _Queue.Queue()
        self._pool = []

    def start(self):
        for i in xrange(self._limit):
            new_thread = _threading.Thread(target = self._do)
            self._pool.append(new_thread)
            new_thread.start()

    def _do(self):
        while True:
            work = self._queue.get()
            command, target , args = work
            if command == "stop":
                break
            elif command == "do":
                try:
                    target(args)
                except:
                    pass
            else:
                logging.error("bakabakabaka!!")

    def apply(self, target, args, command="do"):
        self._queue.put((command, target, args))

    def stop(self):
        for i in xrange(len(self._pool)):
            self.apply("stop",None,None)
        map(lambda t:t.join(), self._pool)
        print "nya"

大致这样,能不能跑我不知道,总之挺简单的。

当然,这个简单的实现忽略了某些细节,比如,这个线程池没法执行有返回值的函数,因为没有储存返回值。修改很简单,增加一个qout队列,然后返回值塞进去,额外加一个方法,去取得返回值。如果需要处理异常,那么再增加qerror就好。

里面有个try except操作。那个是用来防止线程因为异常退出的,不是我写错了……

总之扩充也很容易就是了。

Actor

如果有时间的话,我会另写一篇专门来解释pykka的源码。pykka是github上一个仿akka的实现,后者是Java实现的Actor模型。

Actor模型在鄙司中某些部门有使用到,当时项目的主程专门来做了一次分享解释为什么使用Actor模型,当时听得其实也不太懂就是了。

然后AS去研究了Actor模型,又去照着pykka的api差不多实现了一个Actor,不过还是不觉得有多么好……唔,大概是因为没有特别需要用到的场合吧。

Actor的话,最核心的一点是,Actor之间不会共享状态,很大程度上减少了各种坑,同时利于分布式部署。说到分布式部署,pykka目前还不支持呢……所以AS觉得pykka挺玩具的。话说回来,不玩具的话,AS大概就看不懂代码了,比如Tornado的IOLoop,那个实现AS从来没看懂过,orz。

其实某些地方Actor和线程池还是挺像的,不过想想,大概都是这么回事。

Actor是支持异步的 —- 不过这不是什么特别的事情,实际上吧,只要实现了Future,就可以实现异步。AS以前觉得异步很深奥呢,现在想想,同步是阻塞于某段代码,等待结果的返回;异步呢,就是在那段代码中直接返回了一个结果的许诺而已。当然这个许诺的话,有些喜欢取名叫Future,有些喜欢叫Promise,实际都是一回事吧,AS是这么想的。

Future实现复杂么?不不不,再简单不过了,最简单的Future就是一个maxsize为1的队列而已,恩,真是这样。

通常的Future当然不会只是一个maxsize为1的队列,另外还有一些方法来保证处理异常啊,然后实现各种map啦,filter之类的操作。

另外,tornado的Future并没有使用队列,只是用了一个值阻塞在那里。不过都是一回事。

然后Actor呢,也可以塞一个Future进去,就成了异步了。Actor的组成大致是,一个result是Future的实例,然后有一个inbox,来储存别的actor投递过来的message。

每一个Actor都会阻塞在自己的while循环里面,吐个槽,就while True这个动作,有挺多强悍的名字,比如什么事件循环啊,主循环啊之类的……所以您看,那些新颖的名词好多只是纸老虎。

Actor的事件循环类似于这样:

while not self.is_stopped.is_set():
    message = self.inbox.get()
    dosomething(message)

while not self.inbox.empty():
    do something

其实和线程池挺相似的对吧。

如果有时间,我会写一篇分析分析pykka,不过想想,分析一个玩具程序也没什么必要吧(趴)。

其实还想写点其他的东西的,不过再说吧,其他的包括消息队列啊,gevent啊,AS都在研究中,大致还没有信心写出有干货的分析文章 —- 老实说,翻译教程真无聊啊喵喵喵。

(或许可以写写yield?

总之到此为止,感谢您一如既往的支持。

以上。

梦想变得蔷薇色的AS。