本指南的里代码可以在这里下载: threadworms.py ,或者从 GitHub。代码需求 Python 3 或 Python 2 ,同时也需求安装 Pygame 。
这是一篇为初学者预备的关于 线程 和Python中的多线程编程的指南。 假如你有一些 类(class)的根底常识 (甚么是类,怎么定义办法(method),另有办法老是将self作为他的第一个参数,子类是甚么和子类怎么从父类承继一个办法,等等)这篇指南会对你有所协助。 这里有一篇较为深化地引见类(class)的指南。
我们用到的例子是 “贪吃蛇” 的克隆,它有非常多条在一个格子状的区域前行的蠕虫,每条虫子在一个独自的线程里运转。
假如你晓得线程相干的常识,那就跳过这一章节,看看线程在Python中怎么运用。
当你运转一个通俗的Python程序时,这个程序从第一行Start,一行接一行的的履行。轮回和函数可能让程序高低跳转,可是给定一行代码的地位,你可以随便地找到下一行从那里履行。你可以把一根手指指到你的.py文件中,一行一行的追踪你的程序履行到那里了。这就是单线程编程(single-threaded programming)。
但是,运用多个线程就像将第二跟手指放到屏幕上。每一个手指仍是像之前那样Mobile,可是它们如今是同时在Mobile。
可是现实上,它们并非同时的。你的手指在瓜代着Mobile。具有多中心(multicore)的处置器可以真正意义上的同时履行两条指令,可是Python程序有一个叫做 GIL (全局说明器锁 global interpreter lock) 工具,它会限制Python程序单核履行。
Python的说明器会一会儿履行一个线程,一会儿履行另外一个线程。可是这切换的速度如斯之快,快的让你基本没法发觉,以致于这些线程看起来像是同时履行。
你可以在你的Python程序中开几十或者几百个线程(那真是很多多少的手指)。这其实不能让你的程序快上几十上百倍(现实上这些线程仍是在运用统一个CPU),可是它能让你的程序更弱小,更高效。
举个例子,你要写个函数,这个函数会下载一个内容满是名字的文件,然后将这个文件的内容排序,然后将排序好的内容存为另外一个文件。假如这里有上百个如许的文件,那末你可能会在一个轮回中挪用这个函数来处置每一个文件:下载,排序,保管,下载,排序,保管,下载,排序,保管...
这三个步调用到了你电脑上的分歧资本:下载用到了收集,排序用到了CPU,保管文件用到了硬盘。同时,这三个操作都可能被延缓。例如,你下在文件的Server可能很慢,或者你的带宽很小。
这类状况先,运用多个线程,每一个线程处置一个文件是一个比较明智的选择。这不只能更好的应用你的带宽,并且当你的CPU任务的时分,收集也在任务。这将更有效的应用你的电脑。
固然,在上面的例子,每一个线程只做它本人自力的工作也不需求去和其他线程通讯或同步任何工具。你可以只编写容易的下载-排序-写入程序的单线程版本同时自力地运转程序上百遍。(虽然它可能在每次打字和点击来运转每一个程序来下载分歧文件的时分有点苦楚。)
大大多数多线程程序同享拜访类似的变量,但这就是顺手的工具。
来自 Brad Montgomery的图片)
这里是一个经常使用的比方:通知你有两个售票机械人。它们的Task很容易:
一个主顾问机械A要42号坐位的票。机械A从列表中反省和发明坐位可以用,因而它获得到那张票。但在机械A能从列表中删除改坐位的前,机械B被分歧主顾讯问42号坐位。机械B反省列表也看到了坐位依然可以用,所以它测验考试获得到这个坐位的票。可是机械B不能找到42号坐位的票。这盘算不了了,同机遇器B的电子大脑也爆炸了。机械A在厥后把42号坐位的票从列表上删除。
上面的问题会发作是由于机关两个机械人(或者说,两个线程)都在自力履行,他们二者都在读写一个同享的列表(或者说,一个变量)。你的程序可能很难去修复这类很难重现的bug,由于Python的线程履行切换具有非断定性,那就是,程序每次运转都在做分歧的工具。我们不习气变量里的数据“魔术地”从一行转到下一个仅仅是由于线程在他们之间履行。
当从一个线程的履行切换到别的一个线程,这就是高低文切换。
这也存在死锁的问题,凡是用哲学家就餐问题的比方来说明。五个哲学家围坐一个桌子吃意大利面条,但需求两个叉子。在每一个哲学家之间有一个叉子(总共有5个)。哲学家用这个办法来吃面条:
从实践上他们会和旁边的人同享叉子(我不爱好),这办法看起来仿佛能有效。但顿时或者稍后桌子上每一个人最初都会拿着右边的叉子在手挡同时等候右边的叉子。但由于每一个人都拿着他们旁边的人等候的叉子同时也不会在他们吃之前放下他们,这些哲学家就在一个死锁形态。他们会拿着右边的叉子在手上又永久不会拿到右边的叉子,所以他们永久不会吃到面条也用户不会放下他们左手上的叉子。哲学家都要饿死了(除伏尔泰,它实践上是个机械人。没成心大利面条,他的电子大脑会爆炸)
还存在一种被称为 活锁的状况。当这类状况发作时,一切的线程都让出资本,招致Task不能继续实行下去。就像在大厅里迎面走近的两个人,他们都站到一边,等候对方先过来,后果两个人都卡住了。然后他们又同时试图走到劈面,又相互障碍了对方。他们不断地如许闪开-走近,直到他们都精疲力竭。在多线程编程中,一个防备bug的方法是运用锁。在一个线程读取或者修正该某个同享变量前,它先试图取得一个锁。假如取得了这个锁,这个线程将继续对这个同享变量实行读写。反之,它将不断等候直到这个锁再次可用。
一旦完成对同享变量的操作,线程就会“开释”这个锁。如许其他等候这个锁的线程就可以获得它了。
回到售票机械人的比方。一个机械人把坐位列表拿起来 (这个列表就是锁),反省后发明客户请求的坐位还在,因而把响应的票掏出来,把这个坐位从列表中删去。最初机械人把列表放归去的举措,就相当于“开释了这个锁“。假如另外一个机械人需求检查坐位列表但列表不在,它会不断等候直到坐位列表再次可用。
写代码时,假如遗忘对锁实行开释,便可能引入bug。这将招致死锁状况的发作,由于别的一个等候该锁开释的线程会不断挂在那边无事可做。OK,如今让我们来写一段python程序来讲明怎么运用线程和锁。这段程序基于一个贪吃蛇游戏,是我在拙著《Making Games with Python & Pygame》第六章中克隆的一个版本。这条蛇只会在屏幕上跑来跑去,不会吃苹果。别的,程序中有不止一条蛇。每条蛇由分歧的线程把持。同享变量中的数据构造记载了屏幕上哪一个地位(在这个程序中被成为"格子")被一条蛇盘踞.假如一条蛇曾经在某个格子里了,则其他蛇不能行进到此处并盘踞这个格子。我们将运用锁来包管两条蛇不会盘踞统一个格子。
这篇教程的代码可以今后处下载:threadworms.py 或者GitHub。这份代码兼容Python2和Python3, 别的运转该代码需求安装Pygame.
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务