进程的概念:以一个整体的形式暴露给操作系统管理,里面包含各种资源的调用。 对各种资源管理的集合就可以称为进程。
站在用户的角度思考问题,与客户深入沟通,找到额济纳网站设计与额济纳网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:网站制作、做网站、企业官网、英文网站、手机端网站、网站推广、主机域名、虚拟空间、企业邮箱。业务覆盖额济纳地区。
线程的概念:是操作系统能够进行运算调度的最小单位。本质上就是一串指令的集合。
进程和线程的区别:
1、线程共享内存空间,进程有独立的内存空间。
2、线程启动速度快,进程启动速度慢。注意:二者的运行速度是无法比较的。
3、线程是执行的指令集,进程是资源的集合
4、两个子进程之间数据不共享,完全独立。同一个进程下的线程共享同一份数据。
5、创建新的线程很简单,创建新的进程需要对他的父进程进行一次克隆。
6、一个线程可以操作(控制)同一进程里的其他线程,但是进程只能操作子进程
7、同一个进程的线程可以直接交流,两个进程想要通信,必须通过一个中间代理来实现。
8、对于线程的修改,可能会影响到其他线程的行为。但是对于父进程的修改不会影响到子进程。
第一个程序,使用循环来创建线程,但是这个程序中一共有51个线程,我们创建了50个线程,但是还有一个程序本身的线程,是主线程。这51个线程是并行的。注意:这个程序中是主线程启动了子线程。
相比上个程序,这个程序多了一步计算时间,但是我们观察结果会发现,程序显示的执行时间只有0.007秒,这是因为最后一个print函数它存在于主线程,而整个程序主线程和所有子线程是并行的,那么可想而知,在子线程还没有执行完毕的时候print函数就已经执行了,总的来说,这个时间只是执行了一个线程也就是主线程所用的时间。
接下来这个程序,吸取了上面这个程序的缺点,创建了一个列表,把所有的线程实例都存进去,然后使用一个for循环依次对线程实例调用join方法,这样就可以使得主线程等待所创建的所有子线程执行完毕才能往下走。 注意实验结果:和两个线程的结果都是两秒多一点
注意观察实验结果,并没有执行打印task has done,并且程序执行时间极其短。
这是因为在主线程启动子线程前把子线程设置为守护线程。
只要主线程执行完毕,不管子线程是否执行完毕,就结束。但是会等待非守护线程执行完毕
主线程退出,守护线程全部强制退出。皇帝死了,仆人也跟着殉葬
应用的场景 : socket-server
注意:gil只是为了减低程序开发复杂度。但是在2.几的版本上,需要加用户态的锁(gil的缺陷)而在3点几的版本上,加锁不加锁都一样。
下面这个程序是一个典型的生产者消费者模型。
生产者消费者模型是经典的在开发架构中使用的模型
运维中的集群就是生产者消费者模型,生活中很多都是
那么,多线程的使用场景是什么?
python中的多线程实质上是对上下文的不断切换,可以说是假的多线程。而我们知道,io操作不占用cpu,计算占用cpu,那么python的多线程适合io操作密集的任务,比如socket-server,那么cpu密集型的任务,python怎么处理?python可以折中的利用计算机的多核:启动八个进程,每个进程有一个线程。这样就可以利用多进程解决多核问题。
并发:逻辑上具备同时处理多个任务的能力。
并行:物理上在同一时刻执行多个并发任务。
举例:开个QQ,开了一个进程,开了微信,开了一个进程。在QQ这个进程里面,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。
总结:开一个软件,相当于开了一个进程。在这个软件运行的过程里,多个工作同时运转,完成了QQ的运行,那么这个多个工作分别有多个线程。
线程和进程之间的区别:
进程在python中的使用,对模块threading进行操作,调用的这个三方库。可以通过 help(threading) 了解其中的方法、变量使用情况。也可以使用 dir(threading) 查看目录结构。
current_thread_num = threading.active_count() # 返回正在运行的线程数量
run_thread_len = len(threading.enumerate()) # 返回正在运行的线程数量
run_thread_list = threading.enumerate() # 返回当前运行线程的列表
t1=threading.Thread(target=dance) #创建两个子线程,参数传递为函数名
t1.setDaemon(True) # 设置守护进程,守护进程:主线程结束时自动退出子线程。
t1.start() # 启动子线程
t1.join() # 等待进程结束 exit()`# 主线程退出,t1子线程设置了守护进程,会自动退出。其他子线程会继续执行。
Python进阶(二十六)-多线程实现同步的四种方式
临界资源即那些一次只能被一个线程访问的资源,典型例子就是打印机,它一次只能被一个程序用来执行打印功能,因为不能多个线程同时操作,而访问这部分资源的代码通常称之为临界区。
锁机制
threading的Lock类,用该类的acquire函数进行加锁,用realease函数进行解锁
import threadingimport timeclass Num:
def __init__(self):
self.num = 0
self.lock = threading.Lock() def add(self):
self.lock.acquire()#加锁,锁住相应的资源
self.num += 1
num = self.num
self.lock.release()#解锁,离开该资源
return num
n = Num()class jdThread(threading.Thread):
def __init__(self,item):
threading.Thread.__init__(self)
self.item = item def run(self):
time.sleep(2)
value = n.add()#将num加1,并输出原来的数据和+1之后的数据
print(self.item,value)for item in range(5):
t = jdThread(item)
t.start()
t.join()#使线程一个一个执行12345678910111213141516171819202122232425262728
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“同步阻塞”(参见多线程的基本概念)。
直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。
信号量
信号量也提供acquire方法和release方法,每当调用acquire方法的时候,如果内部计数器大于0,则将其减1,如果内部计数器等于0,则会阻塞该线程,知道有线程调用了release方法将内部计数器更新到大于1位置。
import threadingimport timeclass Num:
def __init__(self):
self.num = 0
self.sem = threading.Semaphore(value = 3) #允许最多三个线程同时访问资源
def add(self):
self.sem.acquire()#内部计数器减1
self.num += 1
num = self.num
self.sem.release()#内部计数器加1
return num
n = Num()class jdThread(threading.Thread):
def __init__(self,item):
threading.Thread.__init__(self)
self.item = item def run(self):
time.sleep(2)
value = n.add()
print(self.item,value)for item in range(100):
在实际处理数据时,因系统内存有限,我们不可能一次把所有数据都导出进行操作,所以需要批量导出依次操作。为了加快运行,我们会采用多线程的方法进行数据处理, 以下为我总结的多线程批量处理数据的模板:
主要分为三大部分:
共分4部分对多线程的内容进行总结。
先为大家介绍线程的相关概念:
在飞车程序中,如果没有多线程,我们就不能一边听歌一边玩飞车,听歌与玩 游戏 不能并行;在使用多线程后,我们就可以在玩 游戏 的同时听背景音乐。在这个例子中启动飞车程序就是一个进程,玩 游戏 和听音乐是两个线程。
Python 提供了 threading 模块来实现多线程:
因为新建线程系统需要分配资源、终止线程系统需要回收资源,所以如果可以重用线程,则可以减去新建/终止的开销以提升性能。同时,使用线程池的语法比自己新建线程执行线程更加简洁。
Python 为我们提供了 ThreadPoolExecutor 来实现线程池,此线程池默认子线程守护。它的适应场景为突发性大量请求或需要大量线程完成任务,但实际任务处理时间较短。
其中 max_workers 为线程池中的线程个数,常用的遍历方法有 map 和 submit+as_completed 。根据业务场景的不同,若我们需要输出结果按遍历顺序返回,我们就用 map 方法,若想谁先完成就返回谁,我们就用 submit+as_complete 方法。
我们把一个时间段内只允许一个线程使用的资源称为临界资源,对临界资源的访问,必须互斥的进行。互斥,也称间接制约关系。线程互斥指当一个线程访问某临界资源时,另一个想要访问该临界资源的线程必须等待。当前访问临界资源的线程访问结束,释放该资源之后,另一个线程才能去访问临界资源。锁的功能就是实现线程互斥。
我把线程互斥比作厕所包间上大号的过程,因为包间里只有一个坑,所以只允许一个人进行大号。当第一个人要上厕所时,会将门上上锁,这时如果第二个人也想大号,那就必须等第一个人上完,将锁解开后才能进行,在这期间第二个人就只能在门外等着。这个过程与代码中使用锁的原理如出一辙,这里的坑就是临界资源。 Python 的 threading 模块引入了锁。 threading 模块提供了 Lock 类,它有如下方法加锁和释放锁:
我们会发现这个程序只会打印“第一道锁”,而且程序既没有终止,也没有继续运行。这是因为 Lock 锁在同一线程内第一次加锁之后还没有释放时,就进行了第二次 acquire 请求,导致无法执行 release ,所以锁永远无法释放,这就是死锁。如果我们使用 RLock 就能正常运行,不会发生死锁的状态。
在主线程中定义 Lock 锁,然后上锁,再创建一个子 线程t 运行 main 函数释放锁,结果正常输出,说明主线程上的锁,可由子线程解锁。
如果把上面的锁改为 RLock 则报错。在实际中设计程序时,我们会将每个功能分别封装成一个函数,每个函数中都可能会有临界区域,所以就需要用到 RLock 。
一句话总结就是 Lock 不能套娃, RLock 可以套娃; Lock 可以由其他线程中的锁进行操作, RLock 只能由本线程进行操作。