Python学习总结【第十四篇】:Python之线程、进程和协程
Python线程
Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import threading import time def show(arg): time.sleep(1) print('thread'+str(arg)) for i in range(10): t = threading.Thread(target=show, args=(i,)) t.start() print('main thread stop') # 结果: main thread stop thread0 thread2 thread1 thread4 thread3 thread8 thread5 thread9 thread6 thread7
上述代码创建了10个"前台"线程,然后控制器交给CPU,CPU根据指定算法进行调度,分片执行指令。
更多方法:
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认),如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
run 线程被cpu调度后自动执行线程对象的run方法
import threading def func(args): print(args) t = threading.Thread(target=func, args=("123",)) t.setDaemon(True) # 默认False True表示主线程不等此子线程 t.start() # 不代表当前线程会被立即执行 # t.join() # 表示主线程到此,等待....直到子线程执行完毕。 t.join(n) 带参数,表示主线程在此最多等待n秒 # func("123") print("end") print("end") print("end") print("end") print("end") print("end")
# 方法1:一般正常创建线程 import threading def f1(arg): print(arg) t = threading.Thread(target=f1, args=(123,)) t.start() # 方法2:通过自定义线程类创建线程 import threading class MyThread(threading.Thread): def __init__(self, func, args): self.func = func self.args = args super(MyThread, self).__init__() # 执行下父类的构造方法 def run(self): self.func(self.args) def f2(arg): print(arg) obj = MyThread(f2, 123) obj.start()
线程锁(Lock,RLock)
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。
import time import threading NUM = 10 def f1(): global NUM NUM -= 1 time.sleep(2) print(NUM) for i in range(10): t = threading.Thread(target=f1,) t.start() # 结果 0 0 0 0 0 0 0 0 0 0
import time import threading NUM = 10 def f1(l): l.acquire() global NUM NUM -= 1 time.sleep(2) print(NUM) l.release() # l = threading.Lock() # 锁不能嵌套添加 RLock可以 lock = threading.RLock() for i in range(10): t = threading.Thread(target=f1, args=(lock,)) t.start() # 结果: 9 8 7 6 5 4 3 2 1 0
信号量(Semaphore)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
#案例1 import threading import time def run(n): semaphore.acquire() time.sleep(1) print("run the thread: %s" % n) semaphore.release() if __name__ == '__main__': num = 0 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 for i in range(20): t = threading.Thread(target=run, args=(i,)) t.start() # 结果 run the thread: 0 run the thread: 1 run the thread: 2 run the thread: 4 run the thread: 3 run the thread: 5 run the thread: 6 run the thread: 7 run the thread: 8 run the thread: 9 run the thread: 10 run the thread: 11 run the thread: 12 run the thread: 13 run the thread: 14 run the thread: 15 run the thread: 16 run the thread: 17 run the thread: 18 run the thread: 19 # 案例2 import threading import time NUM = 10 def func(i, l): global NUM l.acquire() NUM -= 1 time.sleep(2) print(NUM, i) l.release() lock = threading.BoundedSemaphore(5) for j in range(30): t = threading.Thread(target=func, args=(j, lock,)) t.start()
事件(event)
Python线程的事件用于主线程控制其他线程的执行,事件主要提供三个方法:set\wait\clear
事件处理机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞
- clear:将“Flag”设置为False
- set:将“Flag”设置为True
#!/usr/bin/env python # -*- coding:utf-8 -*- import threading def do(event): print('start') event.wait() print('execute') event_obj = threading.Event() for i in range(10): t = threading.Thread(target=do, args=(event_obj,)) t.start() event_obj.clear() inp = input('input:') if inp == 'true': event_obj.set() # 结果: start start start start start start start start start start input:true # 手动输入 execute execute execute execute execute execute execute execute execute execute
import threading def func(i, e): print(i) e.wait() # 检测目前是什么灯,红灯停,绿灯行 print(i+100) event = threading.Event() for i in range(10): t = threading.Thread(target=func, args=(i, event,)) t.start() event.clear() # 设置成红灯 inp = input(">>>") if inp == "1": event.set() # 设置成绿灯 # 结果: 0 1 2 3 4 5 6 7 8 9 >>>1 100 102 104 106 108 101 103 105 107 109
条件(Condition)
使得线程等待,只有满足某条件时,才释放n个线程
import threading def run(n): con.acquire() con.wait() print("run the thread: %s" %n) con.release() if __name__ == '__main__': con = threading.Condition() for i in range(10): t = threading.Thread(target=run, args=(i,)) t.start() while True: inp = input('>>>') if inp == 'q': break con.acquire() con.notify(int(inp)) con.release()
def condition_func(): ret = False inp = input('>>>') if inp == '1': ret = True return ret def run(n): con.acquire() con.wait_for(condition_func) print("run the thread: %s" %n) con.release() if __name__ == '__main__': con = threading.Condition() for i in range(10): t = threading.Thread(target=run, args=(i,)) t.start()
Timer
定时器,指定n秒后执行某操作
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # 结果:等待1s后输出hello worl
自定义线程池
import queue import threading import time class ThreadPool: """ 线程重用问题 线程池直接开到最大,有可能造成浪费 """ def __init__(self, maxsize=5): self.maxsize = maxsize self._q = queue.Queue(maxsize) for i in range(maxsize): self._q.put(threading.Thread) # 把类放入队列中 def get_thread(self): return self._q.get() # 在队列中取一个类 def add_thread(self): # 在队列中加一个类 self._q.put(threading.Thread) pool = ThreadPool(5) # 实例化一个可以存放5个对象的线程池 def task(arg, p): # p 线程池对象 print(arg) time.sleep(2) p.add_thread() # 执行完任务以后,将令牌重新放回线程池 for i in range(100): # threading.Thread类 t = pool.get_thread() obj = t(target=task, args=(i, pool,)) obj.start()
#!/usr/bin/env python # -*- coding:utf-8 -*- # Author:Alex Li import queue import threading import contextlib import time StopEvent = object() class ThreadPool(object): def __init__(self, max_num, max_task_num = None): if max_task_num: self.q = queue.Queue(max_task_num) else: self.q = queue.Queue() self.max_num = max_num self.cancel = False self.terminal = False self.generate_list = [] self.free_list = [] def run(self, func, args, callback=None): """ 线程池执行一个任务 :param func: 任务函数 :param args: 任务函数所需参数 :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) :return: 如果线程池已经终止,则返回True否则None """ if self.cancel: return if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: self.generate_thread() w = (func, args, callback,) self.q.put(w) def generate_thread(self): """ 创建一个线程 """ t = threading.Thread(target=self.call) t.start() def call(self): """ 循环去获取任务函数并执行任务函数 """ current_thread = threading.currentThread self.generate_list.append(current_thread) event = self.q.get() while event != StopEvent: func, arguments, callback = event try: result = func(*arguments) success = True except Exception as e: success = False result = None if callback is not None: try: callback(success, result) except Exception as e: pass with self.worker_state(self.free_list, current_thread): if self.terminal: event = StopEvent else: event = self.q.get() else: self.generate_list.remove(current_thread) def close(self): """ 执行完所有的任务后,所有线程停止 """ self.cancel = True full_size = len(self.generate_list) while full_size: self.q.put(StopEvent) full_size -= 1 def terminate(self): """ 无论是否还有任务,终止线程 """ self.terminal = True while self.generate_list: self.q.put(StopEvent) self.q.empty() @contextlib.contextmanager def worker_state(self, state_list, worker_thread): """ 用于记录线程中正在等待的线程数 """ state_list.append(worker_thread) try: yield finally: state_list.remove(worker_thread) pool = ThreadPool(5) def callback(status, result): # status, execute action status # result, execute action return value pass def action(i): print(i) for i in range(300): ret = pool.run(action, (i,), callback) # time.sleep(5) # print(len(pool.generate_list), len(pool.free_list)) # print(len(pool.generate_list), len(pool.free_list))
补充知识点:上下文管理
import contextlib @contextlib.contextmanager def worker_state(state_list, worker_thread): """ 用于记录线程中正在等待的线程数 """ state_list.append(worker_thread) try: yield finally: state_list.remove(worker_thread) free_list = [] current_thread = "alex" with worker_state(free_list, current_thread): print(123) print(456) # 通过debug查看程序执行过程
如何终止线程池操作? 详细分析自定义线程池(高级版本)的代码
生产者消费者模型
什么是生产这消费者模型?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据 之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。
import queue import threading import time class ThreadPool: """ 线程重用问题 线程池直接开到最大,有可能造成浪费 """ def __init__(self, maxsize=5): self.maxsize = maxsize self._q = queue.Queue(maxsize) for i in range(maxsize): self._q.put(threading.Thread) # 把类放入队列中 def get_thread(self): return self._q.get() # 在队列中取一个类 def add_thread(self): # 在队列中加一个类 self._q.put(threading.Thread) pool = ThreadPool(5) # 实例化一个可以存放5个对象的线程池 def task(arg, p): # p 线程池对象 print(arg) time.sleep(2) p.add_thread() # 执行完任务以后,将令牌重新放回线程池 for i in range(100): # threading.Thread类 t = pool.get_thread() obj = t(target=task, args=(i, pool,)) obj.start()
引入生产者消费者的优势:
1)解耦
2)支持并发
3)支持忙闲不均问题
补充知识:队列
queue模块是Python3中提供队列操作的模块,简单易用,现在我们来介绍队列的相关的知识及工作中常见的应用场景。
1、常见的几种队列讲解
Python中由四种队列分别是:
1)先进先出队列 queue.Queue
2)后进先出队列 queue.LifoQueue
3)优先级队列 queue.ProrityQueue
4)双向队列 queue.deque
2、各种队列详细讲解
import queue q = queue.Queue(10) # 创建一个队列对象,并设置队列长度为10,默认为0 代表队列可以无限长 print("队列大小:%s" % q.maxsize) # 查看目前队列中值的长度 print("目前队列是否为空:%s" % q.empty()) # 判断队列是否为空,如果队列为空,返回True,反之False for i in range(6): q.put(i) # 将值放入队列中 print("队列目前已经存放数据:%s 条" % q.qsize()) print("开始进行取数据操作") for i in range(3): print("第%s次取值为:%s" % (i+1, q.get())) # q.get() 从队列中取值
队列中常见的方法:
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False) 向队列插入值
q.join() 实际上意味着等到队列为空,再执行别的操作
q.put(item, block=True, timeout=None):
q.put_nowait(item) 相当q.put(item, False)
q.qsize() 返回队列的大小
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
import queue q = queue.LifoQueue() q.put(123) q.put(456) print(q.get()) # 结果:456
import queue q = queue.PriorityQueue() q.put((0, "alex")) q.put((2, "ccd")) q.put((3, "linux")) q.put((1, "windows")) q.put((5, "acd")) print(q.get()) print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 结果 (0, 'alex') (1, 'windows') (2, 'ccd') (3, 'linux') (5, 'acd')
import queue q = queue.deque() q.append(123) q.append(345) q.append(222) q.appendleft(456) print(q.pop()) print(q.popleft()) # 结果 222 456
补充知识:全局解释器锁(GIL)
介绍多线程及线程锁,就不得不提及Python的GIL(全局解释器锁)。Python因为GIL的问题表贬不一,因为它在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序中实际开了多少条线程。所以我们经常能发现,Python中的多线程编程有时候效率还不如单线程,就是因为这个原因。
为什么要有GIL?
作为解释型语言,Python的解释器必须做到既安全又高效。我们都知道多线程编程会遇到的问题。解释器要留意的是避免在不同的线程操作内部共享的数据。 同时它还要保证在管理用户线程时总是有最大化的计算资源。那么,不同线程同时访问时,数据的保护机制是怎样的呢?答案是解释器全局锁GIL。GIL对诸如 当前线程状态和为垃圾回收而用的堆分配对象这样的东西的访问提供着保护。
为什么不能去掉GIL?
首先,在早期的python解释器依赖较多的全局状态,传承下来,使得想要移除当今的GIL变得更加困难。其次,对于程序员而言,仅仅是想要理解它的实现就需要对操作系统设计、多线程编程、C语言、解释器设计和CPython解释器的实现有着非常彻底的理解。
在1999年,针对Python1.5,一个“freethreading”补丁已经尝试移除GIL,用细粒度的锁来代替。然而,GIL的移除给单线程程
序的执行速度带来了一定的负面影响。当用单线程执行时,速度大约降低了40%。虽然使用两个线程时在速度上得到了提高,但这个提高并没有随着核数的增加而
线性增长。因此这个补丁没有被采纳。
另外,在python的不同解释器实现中,如PyPy就移除了GIL,其执行速度更快(不单单是去除GIL的原因)。然而,我们通常使用的CPython占有着统治地位的使用量,所以,你懂的。
在Python
3.2中实现了一个新的GIL,并且带着一些积极的结果。这是自1992年以来,GIL的一次最主要改变。旧的GIL通过对Python指令进行计数来确
定何时放弃GIL。在新的GIL实现中,用一个固定的超时时间来指示当前的线程以放弃这个锁。在当前线程保持这个锁,且当第二个线程请求这个锁的时候,当
前线程就会在5ms后被强制释放掉这个锁(这就是说,当前线程每5ms就要检查其是否需要释放这个锁)。当任务是可行的时候,这会使得线程间的切换更加可预测。
GIL对我们有什么影响?
最大的影响是我们不能随意使用多线程。要区分任务场景。
在单核cpu情况下对性能的影响可以忽略不计,多线程多进程都差不多。在多核CPU时,多线程效率较低。GIL对单进程和多进程没有影响。
实际应用场景建议
在IO密集型任务中使用多线程,在计算密集型任务中使用多进程。
相关GIL说明:
原文:Python's Hardest Problem
译文:Python 最难的问题
Python进程
from multiprocessing import Process import threading import time def foo(i): print 'say hi',i for i in range(10): p = Process(target=foo,args=(i,)) p.start()
注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。
进程数据共享
进程各自持有一份数据,默认无法共享数据
如何实现数据共享?
from multiprocessing import Array from multiprocessing import Process def foo(i, arg): arg[i] = i + 100 for item in arg: print(item) print("---------------") if __name__ == "__main__": li = Array("i", 10) for j in range(10): p = Process(target=foo, args=(j, li,)) p.start()
from multiprocessing import Manager from multiprocessing import Process import time def foo(i, arg): arg[i] = i + 100 print(arg.values()) if __name__ == '__main__': obj = Manager() li = obj.dict() for i in range(10): p = Process(target=foo, args=(i, li,)) p.start() # p.join() time.sleep(1)
from multiprocessing import Manager from multiprocessing import Process import time def foo(i, arg): arg[i] = i + 100 print(arg.values()) if __name__ == '__main__': obj = Manager() li = obj.dict() for i in range(10): p = Process(target=foo, args=(i, li,)) p.start() # p.join() time.sleep(1)
'c': ctypes.c_char, 'u': ctypes.c_wchar, 'b': ctypes.c_byte, 'B': ctypes.c_ubyte, 'h': ctypes.c_short, 'H': ctypes.c_ushort, 'i': ctypes.c_int, 'I': ctypes.c_uint, 'l': ctypes.c_long, 'L': ctypes.c_ulong, 'f': ctypes.c_float, 'd': ctypes.c_double
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用的进程为止。
进程池中有两个方法:
- apply
- apply_async
#!/usr/bin/env python # -*- coding:utf-8 -*- from multiprocessing import Process,Pool import time def Foo(i): time.sleep(2) return i+100 def Bar(arg): print arg pool = Pool(5) #print pool.apply(Foo,(1,)) #print pool.apply_async(func =Foo, args=(1,)).get() for i in range(10): pool.apply_async(func=Foo, args=(i,),callback=Bar) print 'end' pool.close() pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
Python协程
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
greenlet
#!/usr/bin/env python # -*- coding:utf-8 -*- from greenlet import greenlet def test1(): print 12 gr2.switch() print 34 gr2.switch() def test2(): print 56 gr1.switch() print 78 gr1 = greenlet(test1) gr2 = greenlet(test2) gr1.switch()
import gevent def foo(): print('Running in foo') gevent.sleep(0) print('Explicit context switch to foo again') def bar(): print('Explicit context to bar') gevent.sleep(0) print('Implicit context switch back to bar') gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar), ])
from gevent import monkey; monkey.patch_all() import gevent import urllib2 def f(url): print('GET: %s' % url) resp = urllib2.urlopen(url) data = resp.read() print('%d bytes received from %s.' % (len(data), url)) gevent.joinall([ gevent.spawn(f, 'https://www.python.org/'), gevent.spawn(f, 'https://www.yahoo.com/'), gevent.spawn(f, 'https://github.com/'), ])
作业:开发一个批量主机管理工具
需求:
- 可以对机器进行分组
- 可以对指定的一组或多组机器执行批量命令,分发文件(发送\接收)
- 纪录操作日志
出处:http://www.cnblogs.com/madsnotes/
声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。