python 实现多线程编程
这篇文章主要是参考Wesley J. Chun的《Python核心编程(第二版)》书籍多线程部分,并结合我以前的一些实例进行简单分析。尤其是在大数据、Hadoop\Spark、分布式开发流行的今天,这些基础同样很重要。希望对你有所帮助吧!
PS:推荐大家阅读《Python核心编程》和《Python基础教程》两本书~
强推:http://www.cnblogs.com/huxi/archive/2010/06/26/1765808.html
一. 线程和进程的概念
二. Python线程和全局解释器锁
- from time import sleep, ctime
- def loop0():
- print ‘Start loop 0 at:’, ctime()
- sleep(4)
- print ‘Loop 0 done at:’, ctime()
- def loop1():
- print ‘Start loop 1 at:’, ctime()
- sleep(2)
- print ‘Loop 1 done at:’, ctime()
- def main():
- print ‘Starting at:’, ctime()
- loop0()
- loop1()
- print ‘All done at:’, ctime()
- if __name__ == ‘__main__’:
- main()
代码的运行结果如下图所示,它将和后面的并行代码做对比。
4.避免使用thread模块
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。
(1) thread模块: 允许程序员创建和管理线程,它提供了基本的线程和锁的支持。
(2) threading模块: 允许程序员创建和管理线程,它提供了更高级别,更强的线程管理的功能。
(3) Queue模块: 允许用户创建一个可用于多个线程间共享数据的队列数据结构。
下面简单分析为什么需要避免使用thread模块?
(1) 首先更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突。
(2) 其次,低级别的thread模块的同步原语很少(实际只有一个),而threading模块则有很多。
(3) 另一个原因是thread对你的进程什么时候应该结束完全没有控制,当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作。而threading模块能确保重要的子线程退出后进程才退出。
当然,为了你更好的理解线程,还是会对thread进行讲解。但是我们只建议那些有经验的专家想访问线程的底层结构时,才使用thread模块。而如果可以,你的第一个线程程序应尽可能使用threading等高级别的模块。
三. thread模块
模块函数 |
描述 |
start_new_thread(function, args kwargs=None) |
产生一个新线程,在新线程中用指定的参数和可选的kwargs来调用该函数 |
allocate_lock() |
分配一个LockType类型的锁对象 |
exit() |
让线程退出 |
类型锁对象方法 |
描述 |
acquire(wait=None) |
尝试获取锁对象 |
locked() |
如果获取了锁对象返回True,否则返回False |
release() |
释放锁 |
start_new_thread()函数是thread模块的一个关键函数,它的语法和内建的apply()函数一样,其参数为:函数,函数的参数以及可选的关键字的参数。不同的是,函数不是在主线程里运行,而是产生一个新的线程来运行这个函数。
2.Thread模块实现代码
现在实现一个线程的代码,与前面没有线程总运行时间为6秒的进行对比。
- import thread
- from time import sleep, ctime
- def loop0():
- print ‘Start loop 0 at:’, ctime()
- sleep(4)
- print ‘Loop 0 done at:’, ctime()
- def loop1():
- print ‘Start loop 1 at:’, ctime()
- sleep(2)
- print ‘Loop 1 done at:’, ctime()
- def main():
- try:
- print ‘Starting at:’, ctime()
- thread.start_new_thread(loop0, ())
- thread.start_new_thread(loop1, ())
- sleep(6)
- print ‘All done at:’, ctime()
- except Exception,e:
- print ‘Error:’,e
- finally:
- print ‘END\n’
- if __name__ == ‘__main__’:
- main()
代码解释:
使用thread模块提供简单的额多线程机制。loop0和loop1并发地被执行(显然,短的那个先结束),总的运行时间为最慢的那个线程的运行时间,而不是所有的线程的运行时间之和。start_new_thread()要求一定要有前两个参数,即使运行的函数不要参数,也要传一个空的元组。
由于采用Python IDLE运行总是报错Runtime,而且已经设置了sleep(6)。运行一个线程勉强能运行,两个线程无论是thread或threading都报错,估计环境配置问题。
最后采用Cygwin Terminal模拟Linux下运行程序。可以发现loop1和loop0是并发执行的,其中loop1先结束运行2秒,而loop0运行4秒。
同时程序主函数中多了个sleep(6),为什么要加这一句话呢?
因为如果我们没有让主线程停下来,那主线程就会运行下一条语句,显示“All done”,然后就关闭运行着loop0和loop1的两个线程,退出了。
我们没有写让主线程停下来等所有子线程结束后再继续运行的代码,这就是前面所说的需要同步的原因。在这里,我们使用sleep(6)作为同步机制。设置6秒,两个线程一个4秒(53-57),一个2秒(53-55),在主线程等待6秒(53-59)后应该已经结束了。
cygwin需要用到的常见用法包括,也可以安装VIM编辑器:
cd c: 进入 ‘c:’ 目录,空格用’\ ‘转义字符
pwd 显示工作路径
ls 查看目录中的文件
file test.py 查看文件内容
python test.py 运行python程序
配置方法见:http://blog.sina.com.cn/s/blog_691ebcfc0101lgme.html
下载地址见:http://pan.baidu.com/s/1jGYEtro
3.线程加锁方法
那么,有什么好的管理线程的方法呢?而不是在主线程里做个额外的延时6秒操作。因为总的运行时间并不比单线程的代码少;而且使用sleep()函数做线程的同步操作是不可靠的;如果循环的执行时间不能事先确定的话,这可能会造成主线程过早或过晚的退出。
这就需要引入锁的概念。下面代码执行loop函数,与前面代码的区别是不用为线程什么时候结束再做额外的等待了。使用锁之后,可以在两个线程都退出后,马上退出。
- #coding=utf-8
- import thread
- from time import sleep, ctime
- 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)) #以loops数组创建列表并赋值给nloops
- for i in nloops:
- lock = thread.allocate_lock() #创建锁对象
- lock.acquire() #获取锁对象 加锁
- locks.append(lock) #追加到locks[]数组中
- #执行多线程 (函数名,函数参数)
- 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 end:’, ctime()
- if __name__ == ‘__main__’:
- main()
运行结果如下:
Starting at: Tue Dec 8 21:57:56 2015
Start loop 0 at: Tue Dec 8 21:57:56 2015
Start loop 1 at: Tue Dec 8 21:57:56 2015
Loop 1 done at: Tue Dec 8 21:57:58 2015
Loop 0 done at: Tue Dec 8 21:58:00 2015
All end: Tue Dec 8 21:58:00 2015
我们在函数中记录下循环的号码和睡眠的时间,同时每个线程都会被分配一个事先已经获得的锁,在sleep()的时间到了之后就释放相应的锁以通知住线程,这个线程已经结束了。
(1) loops[4, 2]定义睡眠时间 nloops=range(len(loops))创建列表[0, 1] 号码;
(2) 调用thread.allocate_lock()函数创建一个锁的列表,并分别调用各个锁的acquire()函数获得锁对象。获得锁表示“把锁锁上”,并放到锁列表locks中;
(3) 再循环创建线程,调用thread.start_new_thread(loop,(i,loops[i],locks[i]))。参数对应线程循环号、睡眠时间和锁。
(4) 在线程结束时,需要做解锁操作,调用lock.release()函数;
(5) 最后一个循环是坐在那一直等待(达到暂停主线程的目的),直到两个锁都被解锁才继续运行。它是顺序检查每个锁,主线程需不停地对所有锁进行检查直到都释放。
为什么我们不在创建锁的循环里创建线程呢?一方面是想实现线程的同步,所以要让“所有的马同时冲出栅栏”;另外获取锁要花些时间,如果线程退出太快,可能导致还没有获得锁,线程就已经结束了。
最后再强调下,thread模块仅仅了解就行,你应该使用更高级别的threading等。
四. threading模块
threading模块不仅提供了Thread类,还提供了各种非常好用的同步机制。如下表列出了threading模块里所有的对象。
threading模块对象 |
描述 |
Thread |
表示一个线程的执行的对象 |
Lock |
锁原语对象(跟thread模块里的锁对象相同) |
RLock |
可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定) |
Condition |
条件变量对象能让一个线程停下来,等待其他线程满足了某个“条件”。如状态的改变或值的改变 |
Event |
通用的条件变量。多个线程可以等待某个时间的发生,在事件发生后,所有的线程都被激活 |
Semaphore |
为等待锁的线程提供一个类似“等候室”的结构 |
BoundedSemaphore |
与Semaphore类似,只是它不允许超过初始值 |
Timer |
与thread类似,只是它要等待一段时间后才开始运行 |
2.Thread类
threading的Thread类是你主要的运行对象。它有很多thread模块里没有的函数。
函数 |
描述 |
start() |
开始线程的执行 |
run() |
定义线程的功能的函数(一般会被子类重写) |
join(timeout=None) |
程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout秒 |
getName() |
返回线程的名字 |
setName(name) |
设置线程的名字 |
isAlive() |
布尔标志,表示这个线程是否还在运行中 |
isDaemon() |
返回线程的daemon标志 |
setDaemon(daemonic) |
把线程的daemon标志设为daemonic(一定要在调用start()函数前调用) |
- #coding=utf-8
- import threading
- from time import sleep, ctime
- 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)) #列表[0,1]
- #创建线程
- for i in nloops:
- t = threading.Thread(target=loop,args=(i,loops[i]))
- threads.append(t)
- #开始线程
- for i in nloops:
- threads[i].start()
- #等待所有结束线程
- for i in nloops:
- threads[i].join()
- print ‘All end:’, ctime()
- if __name__ == ‘__main__’:
- main()
运行结果如下图所示:其中loop0和loop1并行执行,loop1先结束共执行2秒,loop0后结束执行4秒,总共运行时间4秒。注意:此时Start是分行显示了。
所有的线程都创建之后,再一起调用start()函数启动线程,而不是创建一个启动一个。而且,不用再管理一堆锁(分配锁、获得锁、释放锁、检查锁的状态等),只要简单地对每个线程调用join()函数就可以了。
join()会等到线程结束,或者在给了timeout参数的时候,等到超时为止。使用join()比使用一个等待锁释放的无限循环清楚一些(也称“自旋锁”)。
join()的另一个比较重要的方法是它可以完全不用调用。一旦线程启动后,就会一直运行,直到线程的函数结束,退出为止。
如果你的主线程除了等线程结束外,还有其他的事情要做(如处理或等待其他的客户请求),那就不用调用join(),只有在你要等待线程结束的时候才要调用join()。
4.创建一个Thread实例,传给它一个可调用的类对象
这是第二个方法,与传一个函数很相似,但它是传一个可调用的类的实例供线程启动的时候执行,这是多线程编程的一个更为面向对象的方法。相对于一个或几个函数来说,由于类对象里可以使用类请打的功能,可以保存更多的信息,这种方法更为灵活。
- #coding=utf-8
- import threading
- from time import sleep, ctime
- loops = [4,2] #睡眠时间
- class ThreadFunc(object):
- def __init__(self, func, args, name=”):
- self.name=name
- self.func=func
- self.args=args
- def __call__(self):
- apply(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)) #列表[0,1]
- for i in nloops:
- #调用ThreadFunc类实例化的对象,创建所有线程
- 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 end:’, ctime()
- if __name__ == ‘__main__’:
- main()
运行结果如下图所示,传递的是一个可调用的类,而不是一个函数。
创建Thread对象时会实例化一个可调用类ThreadFunc的类对象。这个类保存了函数的参数,函数本身以及函数的名字字符串。
构造器__init__()函数:初始化赋值工作;
特殊函数__call__():由于我们已经有要用的参数,所以就不用再传到Thread()构造器中;由于我们有一个参数的元组,这时要在代码中使用apply()函数。
apply(func [, args [, kwargs ]]) 函数:用于当函数参数已经存在于一个元组或字典中时,间接地调用函数。args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,任何参数都不会被传递,kwargs是一个包含关键字参数的字典。
- def say(a, b):
- print a, b
- apply(say,(”Eastmount”, “Python线程”))
- # 输出
- # Eastmount Python线程
5.Thread派生一个子类,创建这个子类的实例
这是第三个方法,主要是如何子类化Thread类,该方法与第二个方法类似。其中创建子类方法和调用类对象方法的最重要改变是:
(1) MyThread子类的构造器一定要先调用基类的构造器;
(2) 之前特殊函数__call__()在子类中,名字要改为run()。
- #coding=utf-8
- import threading
- from time import sleep, ctime
- loops = [4,2] #睡眠时间
- class MyThread(threading.Thread):
- def __init__(self, func, args, name=”):
- threading.Thread.__init__(self)
- self.name=name
- self.func=func
- self.args=args
- def run(self): #run()函数
- apply(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)) #列表[0,1]
- 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 end:’, ctime()
- if __name__ == ‘__main__’:
- main()
运行结果如下图所示:
6.线程运行斐波那契、阶乘和加和
需要在MyThread类中加入输出信息,除了使用apply()函数运行斐波那契、接触和加和函数外,还把结果保存到实现的self.res属性中,并创建一个函数getResult()得到结果。换句话说,子类方法更加灵活。
- #coding=utf-8
- import threading
- from time import sleep, ctime
- class MyThread(threading.Thread):
- def __init__(self, func, args, name=”):
- threading.Thread.__init__(self)
- self.name=name
- self.func=func
- self.args=args
- def getResult(self):
- return self.res
- def run(self): #run()函数
- print “Starting”, self.name, ‘at:’, ctime()
- self.res = apply(self.func, self.args)
- print self.name, ‘finished at:’, ctime()
在threadfunc.py文件中调用前面定义的Thread子类,myThread.py中的MyThread类。由于这些函数运行得很快(斐波那契函数运行慢些),使用sleep()函数比较它们的时间。实际工作中不需要添加sleep()函数。
- #coding=utf-8
- from myThread import MyThread #myThread.py文件中MyThread类
- from time import sleep, ctime
- #斐波那契函数
- def fib(x):
- sleep(0.005)
- if x < 2:
- return 1
- return (fib(x-2) + fib(x-1))
- #阶乘函数 factorial calculation
- def fac(x):
- sleep(0.1)
- if x < 2:
- return 1
- return (x * fac(x-1))
- #求和函数
- def sum(x):
- sleep(0.1)
- if x < 2:
- return 1
- return (x + sum(x-1))
- funcs = [fib, fac, sum]
- n = 14
- def main():
- nfuncs = range(len(funcs))
- print ‘*****单线程方法*****’
- for i in nfuncs:
- print ‘Starting’, funcs[i].__name__, ‘at:’, ctime()
- print funcs[i](n)
- print ‘Finished’, funcs[i].__name__, ‘at:’, ctime()
- print ‘*****结束单线程*****’
- print ‘ ’
- print ‘*****多线程方法*****’
- threads = []
- for i in nfuncs:
- #调用MyThread类实例化的对象,创建所有线程
- 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 ‘*****结束多线程*****’
- if __name__ == ‘__main__’:
- main()
运行结果如下图所示,单线程运行10s,多线程运行6s。
至于Queue模块这里就不再叙述了。
下面介绍除了各种同步对象和线程对象外,threading模块还提供了一些函数。
函数 |
描述 |
activeCount() |
当前活动的线程对象的数量 |
currentThread() |
返回当前线程对象 |
enumerate() |
返回当前活动线程的列表 |
settrace(func) |
为所有线程设置一个跟踪函数 |
setprofile(func) |
为所有线程设置一个profile函数 |
最后给出一些多线程编程中可能用得到的模块。
模块 |
描述 |
thread |
基本的、低级别的线程模块 |
threading |
高级别的线程和同步对象 |
Queue |
供多线程使用的同步先进先出(FIFO)队列 |
mutex |
互斥对象 |
SocketServer |
具有线程控制的TCP和UDP管理器 |
总之,这篇文章主要是参考《Python核心编程》的,希望文章对你有所帮助~尤其是初学Python编程的,同时为后面我学习多线程的爬虫或分布式爬虫做铺垫。这篇文章花了自己一些时间,写到半夜;写文不易,且看且珍惜吧!勿喷~
(By:Eastmount 2015-12-09 半夜5点 http://blog.csdn.net/eastmount/)
python 实现多线程编程相关推荐
- python进阶 多线程编程 —— threading和queue库实现多线程编程
python进阶 多线程编程 -- threading和queue库实现多线程编程) 摘要 多线程实现逻辑封装 模型参数选择实例 摘要 本文主要介绍了利用python的 threading和queue ...
- 进程、线程及python的多线程编程
目录 一.进程.线程和并行执行 1.什么是进程.线程 注意 2.什么是并行执行 二.python的多线程编程 threading模块 语法 多线程编程的传参 演示 三.总结 一.进程.线程和并行执行 ...
- python --- 基础多线程编程
在python中进行多线程编程之前必须了解的问题: 1. 什么是线程? 答:线程是程序中一个单一的顺序控制流程.进程内一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程 ...
- python之多线程编程(一):基本介绍
Python提供了多个模块来支持多线程编程,包括thread,threading和Queue模块等.编写的程序可以使用thread和threading模块来创建与管理线程. thread模块提供了最基 ...
- 【转】使用python进行多线程编程
1. python对多线程的支持 1)虚拟机层面 Python虚拟机使用GIL(Global Interpreter Lock,全局解释器锁)来互斥线程对共享资源的访问,暂时无法利用多处理器的优势.使 ...
- python多核多线程编程实例_Python多线程
多线程基础概念 并行与并发并行:同时处理多个任务,必须在多核环境下 一段时间内同时处理多个任务,单核也可以并发 并发手段线程:内核空间的调度 进程:内核空间的调度 协程:用户空间的调度 线程可以允许程 ...
- Python Threading 多线程编程
写在篇前 threading模块是python多线程处理包,使用该模块可以很方便的实现多线程处理任务,本篇文章的基础是需要掌握进程.线程基本概念,对PV原语.锁等传统同步处理方法有一定的了解.另外 ...
- python多核多线程编程_python是否支持多处理器/多核编程?
What is the difference between multiprocessor programming and multicore programming? preferably show ...
- 一文看懂Python多进程与多线程编程(工作学习面试必读)
进程(process)和线程(thread)是非常抽象的概念, 也是程序员必需掌握的核心知识.多进程和多线程编程对于代码的并发执行,提升代码效率和缩短运行时间至关重要.小编我今天就来尝试下用一文总结下 ...
最新文章
- C++拾趣——使用多态减少泛型带来的代码膨胀
- STM32-RCC内部总线时钟设置程序详讲
- c语言指针变量字节,C语言指针变量类型和大小
- centos代码切换图形_沙迪克慢走丝代码大全,G代码、T代码、M代码(值得收藏)...
- AOJ0033 Ball【贪心+序列处理】
- web和mysql连接并增删改查_Java Web 使用IDEA对mysql数据库进行简单增删改查操作(附源码下载)...
- 回调函数、Java接口回调 总结
- 单引号(')和双引号()
- k8s集群DNS无法解析问题的处理过程
- 在页面最上面显示当前登陆的状态
- 汤姆克兰西全境封锁服务器维护时间,汤姆克兰西全境封锁无法登录怎么解决 无法登录解决方法攻略...
- Pycharm中无法导入各种Python模块,pip不能更新的解决办法
- php 快速路由,基于FastRoute的快速路由(支持RESTful)
- python、java、C三种方法打印乘法表
- 【详细图解】七彩虹智能主板的开机键连接线怎么插 | 七彩虹主板的前置音频接线法怎么插 | 七彩虹2.0主板 F_PANEL怎样插
- c++ 智能指针 (std::weak_ptr)(一)
- BigDecimal精度控制
- CTF | bugku | 秋名山车神
- DE-MerTOX™ 重金属排毒剂-道格拉斯实验室
- Mac系统安装Node
热门文章
- java bigint范围_java - bigint(20)、smallint(5)
- k8s-身份认证与权限 认证鉴权准入控制- 各种方式带例子-推荐-2023
- 乌云网掌门人方小顿:BAT三巨头对安全问题讳疾忌医
- 十大健康食品和十大垃圾食品
- 2019年5月最新勒索病毒样本分析及数据恢复
- java instance_java关于instance的定义
- python操作摄像头
- 2021年山东省安全员A证作业考试题库及山东省安全员A证实操考试视频
- iOS8 GCD多线程新特性QoS 设置队列优先级
- 数据库应用系统开发方法(知识点总结)