《python核心编程》读书笔记--第18章 多线程编程
18.1引言
在多线程(multithreaded,MT)出现之前,电脑程序的运行由一个执行序列组成。多线程对某些任务来说是最理想的。这些任务有以下特点:它们本质上就是异步的,需要多个并发事务,各个事务的运行顺序可以是不确定的、随机的、不可预测的。这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标。根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。运算密集型任务一般都比较容易分割为多个子任务。
由于顺序执行的程序只有一个线程在运行,它要保证做多个任务,并且不会有某个任务占用太多时间。执行多任务的顺序执行的程序一般程序控制流程都很复杂,难以理解。
使用多线程编程和一个共享的数据结构比如Queue,这种程序任务可以用几个功能单一的线程来组织。
18.2线程和进程
进程
进程是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其他记录其运行轨迹的辅助数据。进程都有自己的内存空间、数据栈等,所以只能使用进程间通讯(interprocess communication,IPC),而不能直接共享信息。
线程
线程跟进程类似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。一个进程的各个线程之间共享一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。但是也有副作用,就是同一片数据,在多个线程访问顺序不同时,可能导致数据不一致问题,当然这是可以解决的。
18.3Python、线程和全局解释器锁
1、全局解释器锁(GIL)
Python代码的执行由Python虚拟机(也称解释器主循环)控制。在一个主循环中,只有一个线程在执行,与单核CPU类似,虽然Python解释器可以运行“多个”线程,但是在任意时刻,只有一个线程在解释器中运行。
以上原理由全局解释器锁(GIL)控制,保证同一时刻只有一个线程在运行。在多线程环境中,Python虚拟机按以下方式执行。
1)设置GIL
2)切换到一个线程去运行
3)运行:
a.指定数量自己码的指令,或者
b.线程主动让出控制(time.sleep())
4)把线程设置为睡眠状态
5)解锁GIL
6)再次重复以上所有步骤。
调用外部扩展(c程序等)时,GIL被锁定,直到这个函数结束为止。
2、退出线程
当一个线程结束计算,就退出了。线程可以调用thread.exit()之类的退出函数,也可以用python退出的标准方法,如sys.exit()或者抛出一个SystemExit异常等。不过不可以直接kill一个线程。
thread和threading两个模块进行进程、线程管理,作者建议不用thread,一个很明显的原因是:thread模块主线程退出时,所有其他线程没有被清除就退出了;threading就能确保所有“重要”的子线程都退出后,程序才会结束。
3、下面看一下单线程
#-*- coding:utf-8 -*-
from time import sleep,ctime def loop0():
print 'start loop0 at:',ctime()
sleep(4)
print 'loop0 done at:',ctime() def loop1():
print 'start loop1 at:',ctime()
sleep(2)
print 'loop1 done at:',ctime() def main():
print 'starting at:',ctime()
loop0()
loop1()
print 'all DONE at:',ctime() if __name__ == '__main__':
main()
当第一个循环执行完之后才会继续执行第二个循环。
4、模块
python提供的几个多线程编程模块,包括thread、threading、Queue等。thread和threading允许我们创建和管理线程。thread提供了基本的线程和锁的支持,而threading提供了更高级别,功能更强的线程管理功能。Queue允许用户创建可以用于多个线程之间共享数据的队列数据结构。
作者再次强调不用thread而是用threading。
18.4Thread模块
两个例子
#-*- coding:utf-8 -*-
from time import sleep,ctime
import thread def loop0():
print 'start loop0 at:',ctime()
sleep(4)
print 'loop0 done at:',ctime() def loop1():
print 'start loop1 at:',ctime()
sleep(2)
print 'loop1 done at:',ctime() def main():
print 'starting at:',ctime()
thread.start_new_thread(loop0,())
thread.start_new_thread(loop1,())
#这个语句是必须有的,主线程必须等待前面两个线程运行完以后再执行最后一句
#然而这样的后果就是:对于单线程,运行时间并不减少;可能不知道每个线程运行多长时间
sleep(6)
print 'all DONE at:',ctime() if __name__ == '__main__':
main()
上面问题的解决方法就是引进锁。
#-*- coding:utf-8 -*-
from time import sleep,ctime
import thread loops = [4,2] def loop(nloop,nsec,lock):
print 'start loop',nloop,' at:',ctime()
sleep(nsec)
print 'loop',nloop,' done at:',ctime()
lock.release() #一个线程运行结束后释放,通知主线程已经结束 def main():
print 'starting at:',ctime()
locks = []
nloops = range(len(loops))
#下面获得锁和执行线程需要分开,不写在一个循环里面,因为:
#要让所有线程同时开始运行;获得锁需要时间,如果有的退出太快,还没获得锁线程就结束了
for i in nloops:
lock = thread.allocate_lock() #分配一个锁对象
lock.acquire() #获取锁对象,表示把锁锁上
locks.append(lock) #锁列表 for i in nloops:
#下面的第二个参数必须有,是一个元组
thread.start_new_thread(loop,(i,loops[i],locks[i])) #最后一个循环用来检查是否所有锁都释放了,其实并没有什么用
for i in nloops:
while locks[i].locked():
pass
print 'all DONE at:',ctime() if __name__ == '__main__':
main()
18.5Threading模块
threading模块中将提供Thread类来实现多线程,并且提供了很好的同步机制。thread模块不支持守护线程,即主线程退出时,不管是否有子线程,都会被强行退出。而threading模块有机制来避免上面的问题。
1、thread类
利用thread类可以有多种方法来创建线程。有三种方法:
- 创建一个Thread类的实例,传给它一个函数
- 创建一个Thread类的实例,传给它一个可调用的类的对象
- 从Thread派生出一个子类,创建一个这个子类的实例
作者推荐最后一种。
第一个例子
实例化一个Thread类,传入一个函数。
#-*- coding:utf-8 -*-
from time import sleep,ctime
import threading loops = [4,2] def loop(nloop,nsec):
print 'start loop',nloop,' at:',ctime()
sleep(nsec)
print 'loop',nloop,' done at:',ctime() def main():
print 'starting at:',ctime()
threads = []
nloops = range(len(loops)) for i in nloops:
#下面实例化Thread类,实例化之后并不马上开始(直到调用start),这样可以更好的同步
t = threading.Thread(target = loop,args = (i,loops[i]))
threads.append(t) for i in nloops:
threads[i].start() for i in nloops:
#线程挂起,直到运行结束
#调用join另一个重要的方面就是它可以完全不用调用,一旦线程启动就会一直运行知道结束
#如果主线程有其他事情做(不止是等待结束),就不用调用join,只有等待结束时才调用join
threads[i].join() print 'all DONE at:',ctime() if __name__ == '__main__':
main()
第二个例子
创建一个Thread的实例,传给它一个可调用的类对象。
#-*- coding:utf-8 -*-
from time import sleep,ctime
import threading loops = [4,2] class ThreadFunc(object):
"""docstring for THreadFunc"""
def __init__(self, func,args,name = ''):
super(ThreadFunc, self).__init__()
self.name = name
self.func = func
self.args = args #为了可以使Thread调用,需要定义call函数,用来执行func函数
def __call__(self):
self.func(*self.args) def loop(nloop,nsec):
print 'start loop',nloop,' at:',ctime()
sleep(nsec)
print 'loop',nloop,' done at:',ctime() def main():
print 'starting at:',ctime()
threads = []
nloops = range(len(loops)) for i in nloops:
#下面实例化Thread类,接收的是一个类实例
t = threading.Thread(target = ThreadFunc(loop,(i,loops[i]),loop.__name__))
threads.append(t) for i in nloops:
threads[i].start() for i in nloops:
threads[i].join() print 'all DONE at:',ctime() if __name__ == '__main__':
main()
第三个例子
创建一个Thread 的实例,传给它一个可调用的类对象。
#-*- coding:utf-8 -*-
from time import sleep,ctime
import threading loops = (4,2)
#print dir(threading.Thread) 查看threading.Thread类中的方法,有run
class MyThread(threading.Thread):
"""docstring for THreadFunc"""
def __init__(self, func,args,name = ''):
super(MyThread, self).__init__()
self.name = name
self.func = func
self.args = args #注意这里的run函数重新定义
def run(self):
self.func(*self.args) def loop(nloop,nsec):
print 'start loop',nloop,' at:',ctime()
sleep(nsec)
print 'loop',nloop,' done at:',ctime() def main():
print 'starting at:',ctime()
threads = []
nloops = range(len(loops)) for i in nloops:
#下面实例化MyThread类
t = MyThread(loop,(i,loops[i]),loop.__name__)
threads.append(t) for i in nloops:
threads[i].start() for i in nloops:
threads[i].join() print 'all DONE at:',ctime() if __name__ == '__main__':
main()
看上面的三个例子,确实感觉第三个更灵活和方便。但是话说回来,本质上还是第一个例子的灵魂加上类的外衣。
下面是一个斐波那契数列的例子。比较单线程和多线程的区别。
先自定义一个MyThread类:
#-*- coding:utf-8 -*-
from time import sleep,ctime
import threading class MyThread(threading.Thread):
"""docstring for THreadFunc"""
def __init__(self, func,args,name = ''):
super(MyThread, self).__init__()
self.name = name
self.func = func
self.args = args def getResult(self):
return self.res #注意这里的run函数重新定义
def run(self):
print 'starting',self.name,'at:',ctime()
self.res = self.func(*self.args)
print self.name,'finished at:',ctime()
下面是三个数值例子。
#-*- coding:utf-8 -*-
from time import sleep,ctime
from myThread import MyThread #Fibonacci数列
def fib(x):
sleep(0.005)
if x < 2:return 1
return (fib(x-2) + fib(x-1)) #阶乘
def fac(x):
sleep(0.1)
if x < 2:return 1
return (x * fac(x-1)) #累加和
def summ(x):
sleep(0.1)
if x < 2:return 1
return (x + summ(x-1)) funcs = [fib,fac,summ]
n = 12 def main():
nfuncs = range(len(funcs)) print '*** SINGLE THREAD'
for i in nfuncs:
print 'starting',funcs[i].__name__,'at:',ctime()
print funcs[i](n)
print funcs[i].__name__,'finished at:',ctime() print '\n*** MULTIPLE THREADS'
threads = []
for i in nfuncs:
t = MyThread(funcs[i],(n,),funcs[i].__name__)
threads.append(t) for i in nfuncs:
threads[i].start() for i in nfuncs:
threads[i].join()
print threads[i].getResult() print 'all DONE.' if __name__ == '__main__':
main()
>>>
*** SINGLE THREAD
starting fib at: Thu Feb 25 14:49:18 2016
233
fib finished at: Thu Feb 25 14:49:20 2016
starting fac at: Thu Feb 25 14:49:20 2016
479001600
fac finished at: Thu Feb 25 14:49:21 2016
starting summ at: Thu Feb 25 14:49:21 2016
78
summ finished at: Thu Feb 25 14:49:22 2016
*** MULTIPLE THREADS
starting fib at: Thu Feb 25 14:49:22 2016
starting fac at: Thu Feb 25 14:49:22 2016
starting summ at: Thu Feb 25 14:49:22 2016
facsumm finished at:finished at: Thu Feb 25 14:49:24 2016
Thu Feb 25 14:49:24 2016
fib finished at: Thu Feb 25 14:49:25 2016
233
479001600
78
all DONE.
[Finished in 7.4s]
可以看出,多线程确实节省了一些时间,但是由于MyThread类的构造,打印顺序有点混乱。另,斐波那契数列用递归太耗时,可以看一下这里的最后部分 http://www.cnblogs.com/batteryhp/p/4987261.html。令,上面的几个for可以用in函数直接运行,不用算长度再取下标。
2、生产者-消费者问题和Queue模块
Queue模块用来进行线程间的通讯,让各个线程之间共享数据。
#-*- coding:utf-8 -*-
from random import randint
from time import sleep
from Queue import Queue
from myThread import MyThread def writeQ(queue):
print 'producing object for Q...',queue.put('xxx',1) #向队列中put进一个
print "size now",queue.qsize() def readQ(queue):
val = queue.get(1) #从队列中取出一个
print 'consumed object from Q... size now',queue.qsize() def writer(queue,loops):
for i in range(loops):
writeQ(queue)
sleep(randint(1,3)) def reader(queue,loops):
for i in range(loops):
readQ(queue)
sleep(randint(2,5)) funcs = [writer,reader]
nfuncs = range(len(funcs)) def main():
nloops = randint(2,5)
q = Queue(32) #定义最大长度为32的队列的同步实现 threads = []
for i in nfuncs:
t = MyThread(funcs[i],(q,nloops),funcs[i].__name__)
threads.append(t) for i in nfuncs:
threads[i].start() for i in nfuncs:
threads[i].join() print 'all DONE.' if __name__ == '__main__':
main()
>>>
starting writer at: Thu Feb 25 15:48:39 2016
producing object for Q... None
size now 1
starting reader at: Thu Feb 25 15:48:39 2016
consumed object from Q... size now 0
producing object for Q... None
size now 1
producing object for Q... None
size now 2
consumed object from Q... size now 1
producing object for Q... None
size now 2
producing object for Q... None
size now 3
consumed object from Q... size now 2
writer finished at: Thu Feb 25 15:48:49 2016
consumed object from Q... size now 1
consumed object from Q... size now 0
reader finished at: Thu Feb 25 15:49:02 2016
all DONE.
[Finished in 23.3s]
由于没有用到锁,所以打印的时候乱七八糟。。。
《python核心编程》读书笔记--第18章 多线程编程的更多相关文章
- 《Clojure编程》笔记 第4章 多线程和并发
目录 背景简述 第4章 多线程和并发 4.0 我的问题 4.1 术语 4.1.1 一个必须要先确定的思考基础 4.2 计算在时间和空间内的转换 4.2.1 delay 4.2.2 future 4.2 ...
- 《Clojure编程》笔记 第2章 函数式编程
目录 背景简述 第2章 函数式编程 背景简述 本人是一个自学一年Java的小菜鸡,理论上跟大多数新手的水平差不多,但我入职的新公司是要求转Clojure语言的.坊间传闻:通常情况下,最好是有一定Jav ...
- 《python核心编程》--读书笔记 第21章 数据库编程
准备:今天拿笔记本装了mysql,这样就能在不同地方用其他电脑远程访问同一个数据库了. python安装MySQLdb模块:http://www.codegood.com/downloads. 21. ...
- 《python核心编程》读书笔记--第16章 网络编程
在进行网络编程之前,先对网络以及互联网协议做一个了解. 推荐阮一峰的博客:(感谢) http://www.ruanyifeng.com/blog/2012/05/internet_protocol_s ...
- WCF服务编程 读书笔记——第1章 WCF基础(2)
续:第1章 WCF基础(1) 元数据交换 服务有两种方案可以发布自己的元数据.一种是基于HTTP-GET协议提供元数据, 另一种则是后面将要讨论的使用专门的终结点的方式.WCF能够为服务自动提供基于H ...
- WCF服务编程 读书笔记——第1章 WCF基础(1)
第1章 WCF基础 本章主要介绍WCF的基本概念.构建模块以及WCF体系架构,以指导读者构建一个简单的WCF服务.从本章的内容中,我们可以了解到WCF的基本术语,包括地址(Address).绑定(Bi ...
- WCF服务编程 读书笔记——第2章 服务契约
操作重载诸如 C++ 和 C# 等编程语言都支持方法重载,即允许具有相同名称的两个方法可以定义不同的参数.例如,如下的 C# 接口就是有效的定义: interface ICalculator { in ...
- APUE读书笔记-第18章-终端I/O
18.1 引言 *终端I/O的用途很广泛,包括用于终端.计算机之间的直接连线.调制解调器以及打印机等等,所以终端I/O系统非常复杂 18.2 综述 *终端I/O有两种不同的工作模式: (1)规范模式输 ...
- #《Essential C++》读书笔记# 第五章 面向对象编程风格
基础知识 继承机制定义了父子(parent/child)关系.父类(parent)定义了所有子类(children)共通的共有接口(public interface)和私有实现(private imp ...
随机推荐
- TCP/IP协议的学习笔记
1.OSI和TCP/IP的协议体系结构 OSI是开放系统互连参考模型,它的七层体系结构概念清楚,理论也比较完整,但它既复杂又不实用.而TCP/IP是一个四层的体系结构,它包含应用层.传输层.网际层和网 ...
- 165. Merge Two Sorted Lists【LintCode by java】
Description Merge two sorted (ascending) linked lists and return it as a new sorted list. The new so ...
- HashMap 阅读
最近研究了一下java中比较常见的map类型,主要有HashMap,HashTable,LinkedHashMap和concurrentHashMap.这几种map有各自的特性和适用场景.使用方法的话 ...
- Java学习笔记-12.传递和返回对象
1.Clone()方法产生一个object,使用方法后必须产生的object赋值. Vector v2 = (Vector)v.clone(); 2.Clone()方法在object中是保护类型方法, ...
- LeetCode 120——三角形最小路径和
1. 题目 2. 解答 详细解答方案可参考北京大学 MOOC 程序设计与算法(二)算法基础之动态规划部分. 从三角形倒数第二行开始,某一位置只能从左下方或者右下方移动而来,因此,我们只需要求出这两者的 ...
- 搭建备份到业务迁移---mysql
mysql安装启动以及配置 使用到阿里云主机直接yum安装以及配置 [root@yunwei-169 mysql]# yum install mysql mysql-server [root@yunw ...
- 3ds Max学习日记(七)
第7章讲的是多边形建模,实例略多,有十六个,再加上周日的怠惰感,只做了几个实例. 附上今日的劳动成果: 布料(创建一个平面,转换为可编辑多边形,然后调整顶点,连接一些边,添加网格平滑,转换为可 ...
- 结对作业二——WordCount进阶版
软工作业三 要求地址 作业要求地址 结对码云项目地址 结对伙伴:秦玉 博客地址 PSP表格 PSP2.1 个人开发流程 预估耗费时间(分钟) 实际耗费时间(分钟) Planning 计划 10 7 · ...
- PAT 甲级 1012 The Best Rank
https://pintia.cn/problem-sets/994805342720868352/problems/994805502658068480 To evaluate the perfor ...
- Delphi XE4 TStringHelper用法详解
原文地址:Delphi XE4 TStringHelper用法详解作者:天下为公 Delphi XE4的TStringHelper,对操作字符串进一步带来更多的方法,估计XE5还能继续用到. Syst ...