1、进程、线程
进程(process)是cpu资源分配的最小单位,线程(thread)是cpu调度的最小单位。多线程和多进程的应用目的是为了提高并发。一个应用程序可以包含多个进程,而一个进程又可以包含多个线程。默认一个应用程序是单进程、单线程。
1)什么是进程(process)
进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。每一个进程都有一个自己的地址空间,即进程空间或(虚空间)。进程空间的大小只与处理机的位数有关,一个 16 位长处理机的进程空间大小为 216 ,而 32 位处理机的进程空间大小为 232 。进程至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。比如打开一个QQ算是一个进程。
2)什么是线程(thread)
线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻量级进程,也是CPU 调度的一个基本单位。
3)进程和线程的区别:
进程是资源分配和拥有的单位,拥有独立的地址空间,同一个进程内的线程共享进程的资源,但线程并不拥有资源,只是使用他们
线程是处理器调度的基本单位,但进程不是。每个进程在创建时,至少需要同时为该进程创建一个线程。即进程中至少要有一个或一个以上的线程,否则该进程无法被调度执行。线程是进程内的一个相对独立的可执行的单元。
由于共享资源【包括数据和文件】,所以线程间需要通信和同步机制,且需要时线程可以创建其他线程,但线程间不存在父子关系。
4)python中关于线程和进程的使用经验:
因为IO操作不占用CPU,因此,IO操作可以使用多线程提高并发。
计算性操作,占用CPU,可以使用多进程提高并发。
5)创建线程
threading提供了一个比thread模块更高层的API来提供线程的并发性
创建线程的基本方法有两种:
1)利用threading.Thread创建对象,在它的初始化函数(__init__)中将可调用对象作为参数传入。
案例1:创建一个子线程,但子线程什么时候被执行,需要服从系统的调用。
虽然代码解释到了t.start(),但实际执行时间取决于系统。
import time
#定义一个主线程
def f1(i):
time.sleep(3) # 需要3秒执行完毕
print(i)
#创建的子线程
import threading
#写完下面一句,就代表创建线程完毕
#target是需要执行的函数,args是传入函数的参数
t=threading.Thread(target=f1,args=(123,))
#执行子线程
t.start()#执行时间待定
#执行主线程
f1(456)
print('end')
执行结果:
案例2:使用setDaemon(True)控制主线程不等子线程。
setDaemon()方法。当参数为True时,把主线程设置为守护线程,主线程A执行结束了,就不管子线程是否完成,一并和主线程退出,也即主线程不等子线程。此方法必须在start() 方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。
import time
#定义一个主线程
def f1(i):
time.sleep(3) # 需要3秒执行完毕
print(i)
#创建的子线程
import threading
#写完下面一句,就代表创建线程完毕
#target是需要执行的函数,args是传入函数的参数
t=threading.Thread(target=f1,args=(123,))
#True表示主线程不等子线程,主线程结束则程序结束。
t.setDaemon(True)
#执行子线程
t.start()#执行时间待定
#执行主线程
f1(456)
print('end')
效果:
案例3:join的参数使用,控制主线程等待子线程多长时间
当一个线程操作需要等待另一个线程执行完毕之后才能继续进行时,使用Join()方法。Join方法会等到使用该方法的线程结束后再执行下面的代码。
import time
#定义一个主线程
def f1(i):
time.sleep(3) # 需要3秒执行完毕
print(i)
#创建的子线程
import threading
#写完下面一句,就代表创建线程完毕
#target是需要执行的函数,args是传入函数的参数
t=threading.Thread(target=f1,args=(123,))
#True表示主线程不等子线程,主线程结束则程序结束。
t.setDaemon(True)
#执行子线程
t.start()#执行时间待定
#主线程执行到join时,等待,直到子线程执行完毕,再继续执行join后的程序
t.join(2)#2表示最多等2秒
print('end')
print('end')
效果:
假设join中的秒数大一点,可能就会有子线程执行完毕
t.join(4)#2表示最多等4秒
2)第二种创建方式,自定义类,通过继承Thread类,重写它的run方法
案例4:自定义线程的使用
#自定义f2方法
def f2(arg):
print(arg)
#创建线程
import threading
class mythread(threading.Thread):#继承threading.Thread方法
def __init__(self,func,args):
self.func=func
self.args=args
# 执行父类的构造方法
super(mythread,self).__init__()
def run(self):
self.func(self.args)
#实例化线程
obj=mythread(f2,123)
#执行线程
obj.start()
threading.Thread类的使用方法:
在自己的线程类的__init__里调用threading.Thread.__init__(self, name = threadname)
Threadname为线程的名字
run(),通常需要重写,编写代码实现做需要的功能。
getName(),获得线程对象名称
setName(),设置线程对象名称
start(),启动线程
jion([timeout]),等待另一线程结束后再运行。
setDaemon(bool),设置子线程是否随主线程一起结束,必须在start()之前调用。默认为False,参数为True时表示主线程不等子线程结束
isDaemon(),判断线程是否随主线程一起结束。
isAlive(),检查线程是否在运行中。
2、线程锁,解决数据共享
同一时刻允许一个线程执行操作。由于线程调用随机,当多个线程都要去修改某一个共享数据的时候,需要对数据访问进行同步。
假设两个线程对象t1和t2都要对num=0进行增1运算,t1和t2都各对num修改10次,num的最终的结果应该为20。但是由于是多线程访问,有可能出现下面情况:在num=0时,t1取得num=0。系统此时把t1调度为”sleeping”状态,把t2转换为”running”状态,t2页获得num=0。然后t2对得到的值进行加1并赋给num,使得num=1。然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给num。这样,明明t1和t2都完成了1次加1工作,但结果仍然是num=1。
threading.Lock类对象,在run方法里,使用lock.acquire()获得了这个锁。此时,其他的线程就无法再获得该锁了,他们就会阻塞在“if lock.acquire()”这里,直到锁被另一个线程释放:lock.release()。
案例5:使用threading.Lock(),上一把锁。
执行结果如下:
案例6:使用threading.RLock(),上多把锁。
执行效果:
3、信号量(Semaphore):批量放行
互斥锁是同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有三个坑,最多只允许三个人同时上厕所,后面的人只能等里面有人出来才能再进去。
案例7:Semaphore批量放行,同时允许5个人同时修改。
执行效果:
5、事件(Event):全部放行或挡住。
python线程的事件用于主线程控制其他线程的执行。事件提供了三个方法set、wait、clear。事件处理的机制,全局定义了一个“Flag”值为False,那么当程序执行evevt.wait方法时,就会阻塞,如果“Flag”值为True,那么event.wait方法时便不再阻塞。
clear:将Flag设置为False,set:将Flag设置为True
案例8:事件event,全部挡住或放行,按照代码从上到下的执行原则,可以写出其执行顺序。
执行结果:
6、条件:使得线程等待,只有满足某条件时,才释放n个线程。
案例9:条件,直接条用condition。
案例10:自己写条件,使用wait_for获取
效果:
7、线程池。
Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码,但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。
线程池需要实现的功能:
设置容量
取一个少一个
无线程时等待
线程执行完毕,交还线程
案例11:使用队列自定义线程池,线程池中放的线程,但是线程不能重复利用
案例12:自定义线程池,在线程池中放任务,线程池的任务存放是用列表的形式,任务有两种类型,一种是实际的任务,一种是在尾部放几个空的任务,空的任务作用是用于识别任务执行结束。
执行结果:打印0-29
8、上下文管理
contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,而任何yield之后的操作都可以放在finally函数中。
案例13:上下文管理案例,看执行顺序
执行结果:
案例14:利用上下文管理,实现socket自动关闭
import contextlib
import socket
@contextlib.contextmanager
def context_socket(host,port):
sk=socket.socket()
sk.bind((host,port))
sk.listen(5)
try:
yield sk#返回sk给with
finally:
sk.close()
#sk返回后,给到sock
with context_socket('127.0.0.1',8888) as sock:
print('执行其他操作')
执行效果:
案例链接:https://pan.baidu.com/s/1mj2U6MG 密码:lxs2