标签:
就个人而言,学了这么多年的课程又写了这么多年的程序,虽然没有涉及到企业级的项目,但还是体会到了有几个知识点是非常重要的,包括:面向对象的思想、如何架构一个项目、设计模式来具体解决问题、应用机器学习和深度学习的方法,当然也包括我这篇文章的内容——多线程和并行化处理数据。 1.为什么引入多线程编程?
在多线程(Multithreaded,MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器CPU中运行。即使整个程序由多个相互独立无关的子任务组成,程序都会顺序执行。
由于并行处理可以大幅度地提升整个任务的效率,故引入多线程编程。
多线程中任务具有以下特点:
(1) 这些任务的本质是异步的,需要有多个并发事务;
(2) 各个事务的运行顺序可以是不确定的、随机的、不可预测的。
这样的编程任务可以分成多个执行流,每个流都有一个要完成的目标。再根据不同的应用,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。
2.什么是进程?
计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据。它们只有在被读取到内存中,被操作系统调用时才开始它们的生命周期。
进程(亦称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其他记录其运行轨迹的辅助数据。操作系统管理在其上运行所有的进程,并为这些进程公平分配时间、进程也可以通过fork和spawn操作来完成其他的任务。
不过进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(interprocess communication, IPC),而不能直接共享信息。
3.什么是线程?
线程(亦称为轻量级进程)跟进程有些相似,不同的是:所有的线程运行在同一个进程中,共享相同的运行环境。它们可以被想象成是在主进程或“主线程”中并行运行的“迷你进程”。
线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断)或暂时的被挂起(睡眠),让其他线程运行,这叫做让步。
一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变成可能。
实际上,在单CPU的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其他的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其他的线程共享运行的结果。
当然,这样的共享并不是完全没有危险的。如果多个线程共同访问同一片数据,则由于数据访问的顺序不同,有可能导致数据结果的不一致的问题,即竞态条件(race condition)。同样,大多数线程库都带有一些列的同步原语,来控制线程的执行和数据的访问。
另一个需要注意的是由于有的函数会在完成之前阻塞住,在没有特别为多线程做修改的情况下,这种“贪婪”的函数会让CPU的时间分配有所倾斜,导致各个线程分配到的运行时间可能不尽相同,不尽公平。
4.简述进程和线程的区别
参考下面三篇文章:
进程和线程关系及区别 - yaosiming2011
进程与线程的区别 - flashsky
应届生经典面试题:说说进程与线程的区别与联系 - way_testlife
1.全局解释器锁(GIL)
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设置之初就考虑到要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样,虽然Python解释器可以“运行”多个线程,但任意时刻,只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(global interpreter lock,GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python虚拟机按一下方式执行:
(1) 设置GIL
(2) 切换到一个线程去运行
(3) 运行:
a. 指定数量的字节码的指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
(4) 把线程设置为睡眠状态
(5) 解锁GIL
(6) 再次重复以上所有步骤
在调用外部代码(如C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于这期间没有Python的字节码被运行,所以不会做线程切换)。编写扩展的程序员可以主动解锁GIL。不过Python开发人员则不用担心在这些情况下你的Python代码会被锁住。
对源代码,解释器主循环和GIL感兴趣的人,可以看看Python/ceval.c文件。
2.退出线程
当一个线程结束计算,它就退出了。线程可以调用thread.exit()之类的退出函数,也可以使用Python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。不过,你不可以直接杀掉Kill一个线程。
后面会讲述两个与线程相关的模块,在这两个模块中,该书中不建议使用thread模块。主要原因是当主线程退出的时候,其他所有线程没有被清除就退出了。而threading模块就能确保所有“重要的”子线程都退出后,进程才会结束。
主线程应该是一个好的管理者,它要了解每个线程都要做些什么事,线程都需要什么数据和什么参数,以及在线程结束的时候,它们都提供了什么结果。这样,主线程就可以把各个线程的结果组成一个有意义的最后结果。
在Python2.7交互式解释器中导入import thread没有报错即表示线程可用。
3.没有线程的例子
使用time.sleep()函数来演示线程的工作,这个例子主要为后面线程做对比。time.sleep()需要一个浮点型的参数,来指定“睡眠”的时间(单位秒)。这就相当于程序的运行会被挂起指定的时间。
代码解释:两个计时器,loop0睡眠4秒,loop1()睡眠2秒,它们是在一个进程或者线程中,顺序地执行loop0()和loop1(),那总运行时间为6秒。有可能启动过程中会再花些时间。
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()代码的运行结果如下图所示,它将和后面的并行代码做对比。
1.基础知识
首先来看看thread模块都提供了些什么。除了产生线程外,thread模块也提供了基本的同步数据结构锁对象(lock object,也叫原语锁、简单锁、互斥锁、互斥量、二值信号量)。同步原语与线程的管理是密不可分的。
常用的线程模块函数
LockType类型锁对象方
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()代码解释:
#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()运行结果如下:
threading模块不仅提供了Thread类,还提供了各种非常好用的同步机制。如下表列出了threading模块里所有的对象。
1.守护线程
其中thread模块需要避免的一个原因是:它不支持守护线程。当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。有时我们并不期望这种行为,这就引入了守护线程的概念。
Threading模块支持守护线程,它们工作流程如下:守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求,它就在那等着。如果你设定一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出时,不用等待这个线程退出,正如网络编程中服务器线程运行在一个无限循环中,一般不会退出的。
如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的daemon属性。即,线程开始(调用thread.start())之前,调用setDaemon()函数设定线程的daemon标准(thread.setDaemon(True))就表示这个线程“不重要”。
如果你想要等待子线程完成再退出,那就什么都不用做,或者显示地调用thread.setDaemon(False)以保证其daemon标志位False。你可以调用thread.isDaemon()函数来判断其daemon标志的值。
新的子线程会继承其父线程的daemon标志,整个Python会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。
2.Thread类
threading的Thread类是你主要的运行对象。它有很多thread模块里没有的函数。
#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是分行显示了。
#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()运行结果如下图所示,传递的是一个可调用的类,而不是一个函数。
def say(a, b): print a, b apply(say,("Eastmount", "Python线程")) # 输出 # Eastmount Python线程
#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()运行结果如下图所示:
#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。
最后给出一些多线程编程中可能用得到的模块。
总之,这篇文章主要是参考《Python核心编程》的,希望文章对你有所帮助~尤其是初学Python编程的,同时为后面我学习多线程的爬虫或分布式爬虫做铺垫。这篇文章花了自己一些时间,写到半夜;写文不易,且看且珍惜吧!勿喷~
(By:Eastmount 2015-12-09 半夜5点 http://blog.csdn.net/eastmount/)
[python] 专题八.多线程编程之thread和threading
标签:
原文地址:http://blog.csdn.net/eastmount/article/details/50155353