今天主要准备系统编程这一块:进程、线程、协程
还记得大学的时候调试的代码吗?一直都是单进程、单线程在跑代码,曾经也想过为什么程序只能这样跑—那是因为自己的学识还不够,见的太少了。以前都是单条腿走路,现在可以多条腿走路类。
(1)引入
现实生活中的多任务,代码中的同步和异步----程序世界中的同步和异步和现实世界中的刚好相反,程序中的同步表示一个任务做完接着做下一个任务,异步表示多个任务可以并行执行的。单核CPU和多核CPU,单核CPU执行代码都是顺序执行,那么单核CPU执行任务是什么样?----这个问题就是多任务怎么执行,答案就是操作系统轮流让各个任务交替执行(任务1执行多少秒,然后切换到任务2…),由于CPU的执行速度实在是太快类,我们感觉就像所有的任务都在同时执行一样。真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多
于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核
心上执行。

(2)进程以及进程的创建
编写完之后的代码,在没有运行的时候,称之为程序,正在运行的代码,就称为进程。进程处理代码以外还需要运行的环境等:进程=运行中的代码+运行代码所需要的环境

—进程创建方式1:
fork()创建--------------典型的fork炸弹
python的os模块封装类常见的系统调用,其中就包括fork,可以在python程序中轻松创建子进程,创建过程:
–程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中
–然后父进程和子进程都会从fork()函数中得到一个返回值,在子进程中这个值一定是0,而父进程中是子进程的id号,可以通过os.getpid()或者os.getppid获得此进程的id以及对应的父进程的id

说明:多进程中,每个进程中所有数据(包括全局变量)都各有拥有一份,互不影响,进程之间独立,互不影响

–创建进程方式2:multiprocessing
由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了一个Process类来代表一个进程对象

#coding=utf-8
from	multiprocessing	import	Process
import	os
#	子进程要执行的代码
def	run_proc(name):
	print('子进程运行中,name=	%s	,pid=%d...'	%	(name,	os.getpid()))
if	__name__=='__main__':
	print('父进程	%d.'	%	os.getpid())
	p	=	Process(target=run_proc,	args=('test',))
	print('子进程将要执行')
	p.start()
	p.join()
	print('子进程已结束')

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个
Process实例,用start()方法启动,这样创建进程比fork()还要简单。
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。使用process不执行join方法,主程序也会等到子进程执行完毕之后在结束主进程。

也可以自己重写Process类来实现进程的创建:
创建新的进程还能够使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象,请看下面的实例:

from	multiprocessing	import	Process
import	time
import	os
#继承Process类
class	Process_Class(Process):
				#因为Process类本身也有__init__方法,这个子类相当于重写了这个方法,
				#但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个
				#最好的方法就是将继承类本身传递给Process.__init__方法,完成这些初始化操作
				def	__init__(self,interval):
								Process.__init__(self)
								self.interval	=	interval
				#重写了Process类的run()方法
				def	run(self):
								print("子进程(%s)	开始执行,父进程为(%s)"%(os.getpid(),os.getppid()
								t_start	=	time.time()
								time.sleep(self.interval)
								t_stop	=	time.time()
								print("(%s)执行结束,耗时%0.2f秒"%(os.getpid(),t_stop-t_start))
if	__name__=="__main__":
				t_start	=	time.time()
				print("当前程序进程(%s)"%os.getpid())								
				p1	=	Process_Class(2)
				#对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法
				p1.start()
				p1.join()
				t_stop	=	time.time()
				print("(%s)执行结束,耗时%0.2f"%(os.getpid(),t_stop-t_start))

创建进程实例的时候和之前差不多,然后进程实例调用start方法,Process内部就会自动调用重写的run方法-----进程所需要执行的操作放在了run里面,之后执行join–主进程会等待子进程执行完毕之后继续往下执行

–进程创建方式3:进程池Pool
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行

进程池核心代码:

po=Pool(3)	#定义一个进程池,最大进程数3
for	i	in	range(0,10):
		#Pool.apply_async(要调用的目标,(传递给目标的参数元祖,))
		#每次循环将会用空闲出来的子进程去调用目标
		po.apply_async(worker,(i,))
print("----start----")
po.close()	#关闭进程池,关闭后po不再接收新的请求
po.join()	#等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")

进程池和Process进程不太一样,这里进程池必须要进行等待才可以,一旦主进程结束,不管进程池是否执行完毕,都会强制结束进程池中的进程,进程池包括非阻塞和阻塞方式调用函数,非阻塞表示所有的任务同时入池操作,阻塞表示一个进程执行完毕之后下一个任务才能入池,通常情况下使用非阻塞的进程池比较多。多任务同时入池

总结:以上就是创建进程的三种方式,fork()、multiprocessing中的Process、以及Pool

(3)进程之间的通信
首先需要明白进程之间是没有关系的,对于全局、局部变量都是各自在各自的内存中,操作之间互不影响,所以进程之间的通信,操作系统提供了许多机制来实现

–1.Queue的使用:使用multiprocessing模块中的Queue实现进程之间的数据传递,通过一个第三方的管道来进行通信
–2.进程池中的进程通信:使用multiprocessing.Manager()中的Queue()

以上就是进程的创建,使用,以及进程之间的通信问题

(4)线程:多线程
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,线程的创建和使用和进程的创建以及使用差不多,但是两种是不同的操作单元,调度者也不同。多线程并发的操作,花费时间要短很多。创建好的线程,需要调用 start()方法来启动,主线程会等待所有的子线程结束后才结束这个和Process创建进程时的操作一致。

#coding=utf-8
import	threading
from	time	import	sleep,ctime
def	sing():
	for	i	in	range(3):
	print("正在唱歌...%d"%i)
	sleep(1)
def	dance():
	for	i	in	range(3):
		print("正在跳舞...%d"%i)
		sleep(1)
if	__name__	==	'__main__':
		print('---开始---:%s'%ctime())
		t1	=	threading.Thread(target=sing)
		t2	=	threading.Thread(target=dance)
		t1.start()
		t2.start()
		#sleep(5)	#	屏蔽此行代码,试试看,程序是否会立⻢结束?
		print('---结束---:%s'%ctime())

核心代码还是:创建线程实例,开启线程实例

线程使用方式2:自定义线程类继承自threading.Thread类

#coding=utf-8
import	threading
import	time
class	MyThread(threading.Thread):
		def	run(self):
				for	i	in	range(3):
					time.sleep(1)
					msg	=	"I'm	"+self.name+'	@	'+str(i)	#name属性中保存的是当前线程的
					print(msg)
if	__name__	==	'__main__':
			t	=	MyThread()
			t.start()

调用start的时候直接调用类中重写的run方法
说明:由于进程和线程的调度由操作系统决定,所有,多进程、多线程的执行顺序是不确定的

线程的总结:

1)每个线程一定会有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字
(2)当线程的run()方法结束的时候该线程完成
(3)无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式
(4)线程的几种状态:新建、就绪、运行、等待、死亡

(6)多线程–共享全局变量
对于多进程来说,全局变量不是共享,每个进程都有一份自己的全局变量,但是多线程就存在全局变量共享,全局变量的共享缺点就是,线程之间对全局变量的随意修改会造成全局变量的混乱,也就是说线程不安全。

(7)进程和线程的简单理解
从功能上理解:
进程:能够完成多任务,在一台电脑上能够同时运行多个wechat
线程:能够完成多任务,比如一个wechat中可以开多个聊天窗口

从定义上:
进程:系统进行资源分配和调度的一个独立单位
线程:线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位
也就是说进程和线程都是由操作系统来调度的

从区别上:
–(1)一个程序至少有一个进程,一个进程至少有一个线程
–(2)线程划分尺度小于进程(资源比进程少),使得多线程程序的并发性高
–(3)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而提高程序的运行效率
–(4)线程不能独立执行,必须依存在进程中

优缺点:线程和进程使用上各有优缺点,线程执行的开销小,但是不利于资源的管理和保护,而进程正好相反

(8)系统编程中的同步
同步-----一件事做完接着做另外一件事,有对接的

(9)线程中的互斥锁
多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制,不然会造成共享数据的混乱,所以在多线程中引入了互斥锁机制,给使用共享数据的代码段进行上锁
—上锁
—释放锁
from threading import Thread, Lock
mutex = Lock()
上锁:mutexFlag = mutex.acquire(True)
解锁:mutex.release()
上锁和解锁之间放的就是操作共享的数据代码片段

锁的好处:确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,在多线程开发中,全局变量是多个线程都共享的数据,而局部变量是各自线程的,是非共享的。死锁状态—死锁就是两个线程之间相互调用时出现的

(8)避免死锁
死锁出现在多线程中,解决死锁的情况是让多个线程有序的执行,同步的应用,可以使用互池锁完成多个任务,有序的进程工作,这就是线程的同步

(9)生产者和消费者模式
–队列:先进先出
–栈:先进后出
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
---------什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,通过第三方来链接生产者和消费者

(10)ThreadLocal
在多线程环境下,每个线程都有自己的数据,一个只有在自己的线程中线程使用自己的局部变量比使用全局变量好,因为局部变量只有自己线程能看到,不影响其他线程,而全局变量的修改必须加锁
使用ThreadLocal的方法创建出来的变量在不同的线程中也是相互没有关系的:

定义过程:

import	threading
#	创建全局ThreadLocal对象:
local_school	=	threading.local()
def	process_student():
				#	获取当前线程关联的student:
				std	=	local_school.student
				print('Hello,	%s	(in	%s)'	%	(std,	threading.current_thread().name))
def	process_thread(name):
				#	绑定ThreadLocal的student:
				local_school.student	=	name
				process_student()
t1	=	threading.Thread(target=	process_thread,	args=('dongGe',),	name=
t2	=	threading.Thread(target=	process_thread,	args=('老王',),	name=
t1.start()
t2.start()
t1.join()
t2.join()

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题

也就是说这个东西创建的方式是全局的,但是在各个线程中使用的是自己的

(11)异步
多个任务并发执行,在主任务执行的时候,各个子任务也是在同步执行的,深刻的理解同步和异步。使用多线程或者多进程来模拟异步的过程