学习Python的那些事儿_Day12_并发编程

  • 思维导图
  • 并发编程
  • Python 多线程
  • Python 多线程概述
  • Python 线程模块
  • 线程同步
  • 线程优先级队列( Queue)
  • Python 多进程编程与multiprocess模块
  • 装饰器


思维导图

python 多线程 报错Exception 内存不释放 python多线程内存越要越大_数据


python 多线程 报错Exception 内存不释放 python多线程内存越要越大_数据_02

并发编程

什么是进程和线程

  • 进程是操作系统分配资源的最小单元, 线程是操作系统调度的最小单元。
  • 一个应用程序至少包括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),使其不在接受新的任务。

  1. 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参数