python成长之路 :线程、进程和协程

时间:2023-03-09 15:49:16
python成长之路 :线程、进程和协程

python成长之路 :线程、进程和协程

python线程

进程与线程的历史

我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心,它承担计算机的所有任务。 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配、任务的调度。 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等。 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构——进程控制 块。 进程就是一个程序在一个数据集上的一次动态执行过程。 进程一般由程序、数据集、进程控制块三部分组成。我们编 写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化 过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

在早期的操作系统里,计算机只有一个核心,进程执行程序的最小单位,任务调度采用时间片轮转的抢占式方式进行进程调度。每个进程都有各自的一块独立的内 存,保证进程彼此间的内存地址空间的隔离。 随着计算机技术的发展,进程出现了很多弊端,一是进程的创建、撤销和切换的开销比较大,二是由于对称多处理机(对称多处理机 (SymmetricalMulti-Processing)又叫SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统 以及总线结构)的出现,可以满足多个运行单位,而多进程并行开销过大。 这个时候就引入了线程的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合 和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。 线程没有自己的系统资源,只拥有在运行时必不可少的资源。但线程可以与同属与同一进程的其他线程共享进程所拥有的其他资源。

进程与线程之间的关系

线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同 一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

 #!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time,datetime def show(arg):
time.sleep(5)
print('thread'+str(arg),time.time()) for i in range(4):
t = threading.Thread(target=show, args=(i,))
# t.setName('name%s' % i)
# print(t.name)
#t.setDaemon(False) #默认是False, 如果改成True,主线程不等待子线程 t.start()
t.run() #立即执行
t.join(2)
print('main thread stop')

上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。

更多方法:

    • start            线程准备就绪,等待CPU调度
    • setName      为线程设置名称
    • getName      获取线程名称
    • setDaemon   设置为后台线程或前台线程(默认为False)
                         如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
                          如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
    • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
    • run              线程被cpu调度后自动执行线程对象的run方法

线程锁threading.RLock和threading.Lock

我们使用线程对数据进行操作的时候,如果多个线程同时修改某个数据,可能会出现不可预料的结果,为了保证数据的准确性,引入了锁的概念

例:假设列表A的所有元素就为0,当一个线程从前向后打印列表的所有元素,另外一个线程则从后向前修改列表的元素为1,那么输出的时候,列表的元素就会一部分为0,一部分为1,这就导致了数据的不一致。锁的出现解决了这个问题。

 #未加锁
import threading
import time gl_num = 0 def show(arg):
global gl_num
time.sleep(1)
gl_num += 1
print(gl_num) for i in range(10):
t = threading.Thread(target=show, args=(i,))
t.start() print('main thread stop')

未加锁代码

 线程锁
import threading
import time gl_num = 0 lock = threading.RLock() #获取一个锁的对象 def Func():
lock.acquire() #创建一把锁
global gl_num #声明全局变量
gl_num +=1
time.sleep(1)
print(gl_num)
lock.release() #更改完释放锁 for i in range(10):
t = threading.Thread(target=Func) #利用线程执行,执行10次 结果1,2,3,4,5,6.。
t.start()

线程锁

threading.Event

Event是线程间通信最间的机制之一:一个线程发送一个event信号,其他的线程则等待这个信号。用于主线程控制其他线程的执行。 Events 管理一个flag,这个flag可以使用set()设置成True或者使用clear()重置为False,wait()则用于阻塞,在flag为 True之前。flag默认为False。

  • Event.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数timeout)。
  • Event.set() :将标识位设为Ture
  • Event.clear() : 将标识伴设为False。
  • Event.isSet() :判断标识位是否为Ture。
 event
import threading def do(event): #定义一个函数,传递参数envent,其实就是传递了一个对象
print 'start'
event.wait() #True则不阻塞
print 'execute' event_obj = threading.Event()
for i in range(10):
t = threading.Thread(target=do, args=(event_obj,)) #传递执行函数和参数
t.start() event_obj.clear() #将“Flag”设置为False
inp = raw_input('input:')
if inp == 'true':
event_obj.set() #set默认为True

event

queue模块

Queue 就是对队列,它是线程安全的

举例来说,我们去肯德基吃饭。厨房是给我们做饭的地方,前台负责把厨房做好的饭卖给顾客,顾客则去前台领取做好的饭。这里的前台就相当于我们的队列。

这个模型也叫生产者-消费者模型。

复制代码

import queue

q = queue.Queue(maxsize=0)  # 构造一个先进显出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。

q.join()    # 等到队列为kong的时候,在执行别的操作
q.qsize() # 返回队列的大小 (不可靠)
q.empty() # 当队列为空的时候,返回True 否则返回False (不可靠)
q.full() # 当队列满的时候,返回True,否则返回False (不可靠)
q.put(item, block=True, timeout=None) # 将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置,
                         为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置的时间,过后,
                         如果队列无法给出放入item的位置,则引发 queue.Full 异常
q.get(block=True, timeout=None) # 移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞,
                      若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置的时候,过后,如果队列为空,则引发Empty异常。
q.put_nowait(item) # 等效于 put(item,block=False)
q.get_nowait() # 等效于 get(item,block=False)

生产者--消费者:

 #!/usr/bin/env python
import Queue
import threading message = Queue.Queue(10) def producer(i):
while True:
message.put(i) def consumer(i):
while True:
msg = message.get() for i in range(12):
t = threading.Thread(target=producer, args=(i,))
t.start() for i in range(10):
t = threading.Thread(target=consumer, args=(i,))
t.start()

生产者消费者模型

线程池

 import queue
import threading
import contextlib
import time StopEvent = object() class ThreadPool(object): def __init__(self, max_num):
self.q = queue.Queue()
self.max_num = max_num 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 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() @contextlib.contextmanager
def worker_state(self, xxx, val):
xxx.append(val)
try:
yield
finally:
xxx.remove(val) 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)
status = True
except Exception as e:
status = False
result = e if callback is not None:
try:
callback(status, result)
except Exception as e:
pass if self.terminal: # False
event = StopEvent
else:
# self.free_list.append(current_thread)
# event = self.q.get()
# self.free_list.remove(current_thread)
with self.worker_state(self.free_list, current_thread):
event = self.q.get()
else:
self.generate_list.remove(current_thread) def close(self):
num = len(self.generate_list)
while num:
self.q.put(StopEvent)
num -= 1 # 终止线程(清空队列)
def terminate(self): self.terminal = True while self.generate_list:
self.q.put(StopEvent)
self.q.empty()
import time def work(i):
print(i,"----") pool = ThreadPool(10)
for item in range(50):
pool.run(func=work, args=(item,)) pool.terminate()

完整版线程池

 #!/usr/bin/env python
# -*- coding:utf-8 -*-
import queue
import threading
import contextlib
import time StopEvent = object() #定义标记的意义在于任务结束后退出的标记 class ThreadPool(object): def __init__(self, max_num):
self.q = queue.Queue() #定义队列无限大
self.max_num = max_num #定义最大值 self.terminal = False #定义为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 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)#并执行call方法
t.start() def call(self):
"""
循环去获取任务函数并执行任务函数
"""
current_thread = threading.currentThread #拿到当前线程
self.generate_list.append(current_thread) #添加到正在使用线程队列 event = self.q.get() #--这里是一个获取到的元组w=()....
while event != StopEvent: func, arguments, callback = event # w= (func, args, callback)
try:
result = func(*arguments) #执行任务 ret = aaa() --def aaa(): return 1
status = True
except Exception as e: #如果我这个任务报错
status = False
result = e if callback is not None: #这是一个返回值
try:
callback(status, result)
except Exception as e:
pass self.free_list.append(current_thread) #---记得看上下文代码,执行完任务,把这个线程放到空闲队列里面
event = self.q.get()#当前的状态应该是没任务,线程等待任务,不结束
self.free_list.remove(current_thread) #获取任务移除休息线程
else:
if event = StopEvent:
self.generate_list.remove(current_thread) #移除当前正在运行的线程,等他运行完 def close(self):
num = len(self.generate_list)
while num:
self.q.put(StopEvent)#
num -= 1 import time def work(i):
print(i) pool = ThreadPool(10) #定义最大线程为10个,实例化,并初始化
for item in range(50): #创建了50个任务
pool.run(func=work, args=(item,)) #执行work函数,args是传参 pool.close() #关闭线程池

理解注释版线程池

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()

注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。

multiprocessing模块

multiprocessing是python的多进程管理包,和threading.Thread类似。直接从侧面用subprocesses替换线程使用GIL的方式,由于这一点,multiprocessing模块可以让程序员在给定的机器上充分的利用CPU。

在multiprocessing中,通过创建Process对象生成进程,然后调用它的start()方法,

各种参数的意思:

注意:

  start() 开始进程

  setDaemon(True):守护进程:mutilprocess.setDaemon(True)

  terminate():退出进程:最好使用 poison pill,强制的使用terminate()

  is_alive(): 进程创建之后,可以使用multiprocessing对象的is_alive方法查看线程是否运行,为True则运行

  join():阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。|  或者也可以理解为: 进程池中进程执行完毕后在关闭, 如果注释, 那么程序直接关闭

守护进程


守护进程就是不阻挡主程序退出,自己干自己的 mutilprocess.setDaemon(True)

就这句

等待守护进程退出,要加上join,join可以传入浮点数值,等待n久就不等了

import multiprocessing
import time
import sys def daemon():
name = multiprocessing.current_process().name
print 'Starting:', name
time.sleep(2)
print 'Exiting :', name def non_daemon():
name = multiprocessing.current_process().name
print 'Starting:', name
print 'Exiting :', name if __name__ == '__main__':
d = multiprocessing.Process(name='daemon',
target=daemon)
d.daemon = True n = multiprocessing.Process(name='non-daemon',
target=non_daemon)
n.daemon = False d.start()
n.start() d.join(1)
print 'd.is_alive()', d.is_alive()
n.join()

终止进程


最好使用 poison pill,强制的使用terminate()

注意 terminate之后要join,使其可以更新状态

import multiprocessing
import time def slow_worker():
print 'Starting worker'
time.sleep(0.1)
print 'Finished worker' if __name__ == '__main__':
p = multiprocessing.Process(target=slow_worker)
print 'BEFORE:', p, p.is_alive() p.start()
print 'DURING:', p, p.is_alive() p.terminate()
print 'TERMINATED:', p, p.is_alive() p.join()
print 'JOINED:', p, p.is_alive()

简单示例:


 from multiprocessing import Process

 def f(name):
print('hello', name) if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
p.join()

join()方法的示例:


from multiprocessing import Process
import os, time, random def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random()) if __name__ == "__main__":
print "main process run..."
p1 = Process(target=r1, args=('process_name1', ))
p2 = Process(target=r2, args=('process_name2', )) p1.start()
p2.start()
#p1.join()
#p2.join()
print "main process runned all lines..."

进程数据共享

进程各自持有一份数据,默认无法共享数据

 #!/usr/bin/env python
#coding:utf-8 from multiprocessing import Process
from multiprocessing import Manager import time li = [] def foo(i):
li.append(i)
print 'say hi',li for i in range(10):
p = Process(target=foo,args=(i,))
p.start() print 'ending',li

进程间无数据共享

 #方法一,Array
from multiprocessing import Process,Array
temp = Array('i', [11,22,33,44]) def Foo(i):
temp[i] = 100+i
for item in temp:
print i,'----->',item for i in range(2):
p = Process(target=Foo,args=(i,))
p.start() #方法二:manage.dict()共享数据
from multiprocessing import Process,Manager manage = Manager()
dic = manage.dict() def Foo(i):
dic[i] = 100+i
print dic.values() for i in range(2):
p = Process(target=Foo,args=(i,))
p.start()
p.join()
     '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

当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

 #!/usr/bin/env python
# -*- coding:utf-8 -*- from multiprocessing import Process, Array, RLock def Foo(lock,temp,i):
"""
将第0个数加100
"""
lock.acquire()
temp[0] = 100+i
for item in temp:
print i,'----->',item
lock.release() lock = RLock()
temp = Array('i', [11, 22, 33, 44]) for i in range(20):
p = Process(target=Foo,args=(lock,temp,i,))
p.start()

进程锁实例

进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

  • 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)#但它是非阻塞且支持结果返回进行回调 (回调 ret = pool.apply....) print 'end'
pool.close()#关闭进程池,不再接受新的进程
 pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
 #apply和apply_async

 from multiprocessing import Pool
import time def f1(a):
time.sleep(1)
print(a)
return 1000
def f2(arg):
print(arg) if __name__ == '__main__':
pool = Pool(5)
for i in range(10):
pool.apply_async(func=f1, args=(i,), callback=f2)
#pool.apply(func=f1, args=(i,))
print('') pool.close()
pool.join()

window环境代码

协程

线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。

协程存在的意义:对于多线程应用,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()

gevent

 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),
])

遇到IO操作自动切换:

 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/'),
])