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. TCP/IP协议的学习笔记

    1.OSI和TCP/IP的协议体系结构 OSI是开放系统互连参考模型,它的七层体系结构概念清楚,理论也比较完整,但它既复杂又不实用.而TCP/IP是一个四层的体系结构,它包含应用层.传输层.网际层和网 ...

  2. 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 ...

  3. HashMap 阅读

    最近研究了一下java中比较常见的map类型,主要有HashMap,HashTable,LinkedHashMap和concurrentHashMap.这几种map有各自的特性和适用场景.使用方法的话 ...

  4. Java学习笔记-12.传递和返回对象

    1.Clone()方法产生一个object,使用方法后必须产生的object赋值. Vector v2 = (Vector)v.clone(); 2.Clone()方法在object中是保护类型方法, ...

  5. LeetCode 120——三角形最小路径和

    1. 题目 2. 解答 详细解答方案可参考北京大学 MOOC 程序设计与算法(二)算法基础之动态规划部分. 从三角形倒数第二行开始,某一位置只能从左下方或者右下方移动而来,因此,我们只需要求出这两者的 ...

  6. 搭建备份到业务迁移---mysql

    mysql安装启动以及配置 使用到阿里云主机直接yum安装以及配置 [root@yunwei-169 mysql]# yum install mysql mysql-server [root@yunw ...

  7. 3ds Max学习日记(七)

      第7章讲的是多边形建模,实例略多,有十六个,再加上周日的怠惰感,只做了几个实例. 附上今日的劳动成果:   布料(创建一个平面,转换为可编辑多边形,然后调整顶点,连接一些边,添加网格平滑,转换为可 ...

  8. 结对作业二——WordCount进阶版

    软工作业三 要求地址 作业要求地址 结对码云项目地址 结对伙伴:秦玉 博客地址 PSP表格 PSP2.1 个人开发流程 预估耗费时间(分钟) 实际耗费时间(分钟) Planning 计划 10 7 · ...

  9. PAT 甲级 1012 The Best Rank

    https://pintia.cn/problem-sets/994805342720868352/problems/994805502658068480 To evaluate the perfor ...

  10. Delphi XE4 TStringHelper用法详解

    原文地址:Delphi XE4 TStringHelper用法详解作者:天下为公 Delphi XE4的TStringHelper,对操作字符串进一步带来更多的方法,估计XE5还能继续用到. Syst ...