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章 多线程编程的更多相关文章

  1. 《Clojure编程》笔记 第4章 多线程和并发

    目录 背景简述 第4章 多线程和并发 4.0 我的问题 4.1 术语 4.1.1 一个必须要先确定的思考基础 4.2 计算在时间和空间内的转换 4.2.1 delay 4.2.2 future 4.2 ...

  2. 《Clojure编程》笔记 第2章 函数式编程

    目录 背景简述 第2章 函数式编程 背景简述 本人是一个自学一年Java的小菜鸡,理论上跟大多数新手的水平差不多,但我入职的新公司是要求转Clojure语言的.坊间传闻:通常情况下,最好是有一定Jav ...

  3. 《python核心编程》--读书笔记 第21章 数据库编程

    准备:今天拿笔记本装了mysql,这样就能在不同地方用其他电脑远程访问同一个数据库了. python安装MySQLdb模块:http://www.codegood.com/downloads. 21. ...

  4. 《python核心编程》读书笔记--第16章 网络编程

    在进行网络编程之前,先对网络以及互联网协议做一个了解. 推荐阮一峰的博客:(感谢) http://www.ruanyifeng.com/blog/2012/05/internet_protocol_s ...

  5. WCF服务编程 读书笔记——第1章 WCF基础(2)

    续:第1章 WCF基础(1) 元数据交换 服务有两种方案可以发布自己的元数据.一种是基于HTTP-GET协议提供元数据, 另一种则是后面将要讨论的使用专门的终结点的方式.WCF能够为服务自动提供基于H ...

  6. WCF服务编程 读书笔记——第1章 WCF基础(1)

    第1章 WCF基础 本章主要介绍WCF的基本概念.构建模块以及WCF体系架构,以指导读者构建一个简单的WCF服务.从本章的内容中,我们可以了解到WCF的基本术语,包括地址(Address).绑定(Bi ...

  7. WCF服务编程 读书笔记——第2章 服务契约

    操作重载诸如 C++ 和 C# 等编程语言都支持方法重载,即允许具有相同名称的两个方法可以定义不同的参数.例如,如下的 C# 接口就是有效的定义: interface ICalculator { in ...

  8. APUE读书笔记-第18章-终端I/O

    18.1 引言 *终端I/O的用途很广泛,包括用于终端.计算机之间的直接连线.调制解调器以及打印机等等,所以终端I/O系统非常复杂 18.2 综述 *终端I/O有两种不同的工作模式: (1)规范模式输 ...

  9. #《Essential C++》读书笔记# 第五章 面向对象编程风格

    基础知识 继承机制定义了父子(parent/child)关系.父类(parent)定义了所有子类(children)共通的共有接口(public interface)和私有实现(private imp ...

随机推荐

  1. TW实习日记:第31-32天

    不知不觉的,实习的净工作天数,已经都超过一个月了.因为对工作内容不是很满意,所以打算月底离职,也不知道是公司太缺人还是我真的能干活,领导竟然三番两次找我让我再考虑...明天又要找我了,哎...随机应变 ...

  2. sparksql读写hbase

    //写入hbase(hfile方式) org.apache.hadoop.hbase.client.Connection conn = null; try { SparkLog.debug(" ...

  3. maven 安装、配置

    简介: maven 大大提高项目开发速度     编译---打包---测试--安装   一条龙 maven将项目构建的过程标准化,每一个阶段使用一个命令完成,下面是构建过程一些阶段 清理 mvn cl ...

  4. springMVC怎么改变form的提交方式为put或者delete

    想着练习一下创建restful风格的网站呢,结果发现在jsp页面上并不能灵活使用put和delete提交方式.下面我的解决办法 一. form 只支持post和get两种提交方式,只支持get提交方式 ...

  5. SpringBoot项目打包成jar后,启动脚本

    将springboot项目打包成jar后,上传至服务器,每次都需要手敲命令,重新部署项目,可将这些命令写入脚本中,直接运行. 启动脚本(start.sh): CUR_PATH=$(cd "$ ...

  6. c# html 导出excel

    [CustomAuthorize]        public FileResult ExportCustomerManagerVisitExcel(string dateType, string r ...

  7. Bower 显示‘bower ESUDO Cannot be run with sudo’的错误解决方法

    使用 sudo 命令后或者当前用户为 root,执行 bower 相关命令会出现错误: 解决办法: 在命令后面加 --allow-root 例: bower init  --allow-root bo ...

  8. Python运行的方式

    Python的运行方式多种多样,下面列举几种: 交互式 在命令行中输入python,然后在>>>提示符后面输入Python语句,这里需要注意: 1 语句前面不能有空格,否则会报错 2 ...

  9. 软件工程第二周PSP

  10. TCP系列25—重传—15、DSACK虚假重传探测

    一.DSACK介绍 RFC2883通过指定使用SACK来指示接收端的重复包(duplicate packet)扩展了RFC2018对SACK选项的定义(SACK选项的介绍和示例参考前面内容).RFC2 ...