学习Python的那些事儿_Day12_并发编程
- 思维导图
- 并发编程
- Python 多线程
- Python 多线程概述
- Python 线程模块
- 线程同步
- 线程优先级队列( Queue)
- Python 多进程编程与multiprocess模块
- 装饰器
思维导图
并发编程
什么是进程和线程
- 进程是操作系统分配资源的最小单元, 线程是操作系统调度的最小单元。
- 一个应用程序至少包括1个进程,而1个进程包括1个或多个线程,线程的尺度更小。
- 每个进程在执行过程中拥有独立的内存单元,而一个线程的多个线程在执行过程中共享内存。
Python 多线程
Python 多线程概述
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
线程在执行过程中与进程还是有区别的。每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) – 这就是线程的退让
Python中使用线程有两种方式:函数或者用类来包装线程对象。
函数式:调用thread模块中的start_new_thread()函数来产生新线程。语法如下:
thread.start_new_thread ( function, args[, kwargs] )
Python 线程模块
Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁。
threading 模块提供的其他方法:
- threading.currentThread(): 返回当前的线程变量。
- threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
- run(): 用以表示线程活动的方法。
- start():启动线程活动。
- join([time]):等待至线程中止。这阻塞调用线程直至线程的
- join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
- isAlive(): 返回线程是否活动的。
- getName(): 返回线程名。
- setName(): 设置线程名。
使用Threading模块创建线程
使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:
线程同步
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。如下:
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。
考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。
那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。
锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。
经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。
线程优先级队列( Queue)
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
Queue模块中的常用方法:
- Queue.qsize() 返回队列的大小 Queue.empty() 如果队列为空,返回True,反之False
- Queue.full() 如果队列满了,返回True,反之False Queue.full 与 maxsize 大小对应
- Queue.get([block[, timeout]])获取队列,timeout等待时间
- Queue.get_nowait()相当Queue.get(False)
- Queue.put(item) 写入队列,timeout等待时间
- Queue.put_nowait(item) 相当Queue.put(item, False)
- Queue.task_done()在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信
- Queue.join()实际上意味着等到队列为空,再执行别的操作
Python 多进程编程与multiprocess模块
python的多进程编程主要依靠multiprocess模块。
很多时候系统都需要创建多个进程以提高CPU的利用率,当数量较少时,可以手动生成一个个Process实例。当进程数量很多时,或许可以利用循环,但是这需要程序员手动管理系统中并发进程的数量,有时会很麻烦。这时进程池Pool就可以发挥其功效了。可以通过传递参数限制并发进程的数量,默认值为CPU的核数。
Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果进程池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。
下面介绍一下multiprocessing 模块下的Pool类的几个方法:
1.apply_async
函数原型:apply_async(func[, args=()[, kwds={}[, callback=None]]])
其作用是向进程池提交需要执行的函数及参数, 各个进程采用非阻塞(异步)的调用方式,即每个子进程只管运行自己的,不管其它进程是否已经完成。这是默认方式。
2.map()
函数原型:map(func, iterable[, chunksize=None])
Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到结果返回。 注意:虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。
3.map_async()
函数原型:map_async(func, iterable[, chunksize[, callback]])
与map用法一致,但是它是非阻塞的。其有关事项见apply_async。
4.close()
关闭进程池(pool),使其不在接受新的任务。
- terminate()
结束工作进程,不在处理未处理的任务。
6.join()
主进程阻塞等待子进程的退出, join方法要在close或terminate之后使用。
装饰器
装饰器,顾名思义,就是增强函数或类的功能的一个函数。
#定义装饰器
def time_calc(func):
def wrapper(*args,**kargs):
start_time = time.time()
f = func(*args,**kargs)
exec-time = time.time() - start_time
return f
return wrapper
#使用装饰器
@time_clac
def add(a,b):
return a + b
@time_clac
def sub(a,b):
return a - b
装饰器的作用:增强函数的功能,确切的说,可以装饰函数,也可以装饰类
装饰器的原理:函数是Python的一等公民,函数也是对象
定义装饰器
def decorator(func):
def wrapper(*args,**kargs):
# 可以自定义传入的参数
print(func._name_)
# 返回传入的方法名参数的调用
return func(*args,**kargs)
# 返回内层函数函数名
return wrapper
使用装饰器
假设decorator是定义好的装饰器
方法一:不用语法糖@符号
#装饰器不传入参数时
f = decorator(函数名)
#装饰器传入参数时
f = (decorator(参数))(函数名)
f = (decorator(参数))(函数名)
方法二:采用语法糖@符号
#已定义的装饰器
@decortor
def f():
pass
#执行被装饰过的函数
f()
内置装饰器
常见的内置装饰器有三种 @property @staticmethod @classmethod
@property
把类内方法当成属性来使用,必须要有返回值,相当于getter:
假如没有定义@func.setter 修饰方法的话,就是只读属性
class Car:
def _init_(self,name,price):
self._name = name
self._price = price
@property
def car_name(self):
return self._name
# car_name可以读写的属性
@car_name.setter
def car_name(self,value):
self._name = value
# car_prince是只读属性
@property
def car_price(self):
return str(self._price) + '万'
benz = Car('benz',30)
print(benz.car_name) #benz
benz.car_name = "baojun"
print(benz.car_name) #baojun
print(benz.car_prince) # 30万
@staticmethod
静态方法,不需要表示资深对象的self和自身类的cls参数,就跟使用函数一样
@classmethod
类方法,不需要self参数,但第一个参数需要是表示自身类的cls参数