让你的代码更灵活:进程和线程
进程和线程概念
进程和线程都是操作系统提供的,作用:就是为了让程序在同一时间处理多个任务的方法。
进程
程序并不能单独运行,只有将程序加载到内存中,系统为它分配资源后才能运行,而这种执行的程序就称为进程。
程序和进程的区别
程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,允许多个程序同时加载到内存中,在操作系统的调度下可以实现并发执行。这样设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此进程就是为了在CPU上实现多道编程而提出的。进程有很多优点,它提供了多道编程,让我们每个人感觉都拥有自己的CPU和其它资源,可以提高计算机的利用率。
那为什么还要线程呢?进程有如下缺点:
- 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能无力了。
- 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
例如:qq聊天,qq作为一个独立进程如果同一时间只能干一件事,那它如何实现同一时刻,既能监听键盘输入、又能监听其他人发给你的消息、还能把别人发的消息显示在屏幕上呢?(操作系统分时指不同进程间的分时,即操作系统处理一会你的qq任务,又切换到word文档任务上,每个cpu时间片分给你qq程序时,你的qq还是只能同时干一件事)
再直白一点,一个操作系统像一个工厂,工厂里面有很多生产车间,不同车间生产不同的产品,每个车间就相当于一个进程,而你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,工厂电工只能给不同车间分时供电,但是轮到你的QQ车间时,发现只有一个干活的工人,结果生产效率低,为了解决这个问题,就是加多几个工人,让他们并行工作,这几个工人就是线程。
线程:线程是容器中的工作单位,是CPU调度的基本单位,一个进程中可以并发多个线程,每条线程并行执行不同任务。
操作系统设计的目的
- 已多进程的形式,允许多个任务同时运行;
- 已多线程的形式,允许单个任务分成不同的部分运行;
- 提供协调机制,一方面处理进程和线程之间产生的冲突,另一方面允许进程和线程之间共享资源;
进程(Process)
运行一个程序时,这个程序的代码会被操作系统加载到内存中,并创建一个进程来承载和运行它。
简单来说,每一个运行中的程序就是一个进程,这个进程被称为主进程。
在主进程中,可以创建子进程来协助处理其它任务,这是主进程和子进程是并行运行的。子进程也可以有它的子进程,从而形成以主进程为根的一颗进程树。
创建进程:
__authon__ = 'tian'
__date__ = '2019/12/26 22:54' import multiprocessing import os def target_func(): print('子进程运行')
print('子进程pid:',os.getpid()) #os.getpid()获取进程的ID print('子进程的ppid',os.getppid())#os.getppid()获取进程的父进程ID,父进程是创建子进程的进程
if __name__ == '__main__':
p = multiprocessing.Process(target=target_func)#创建进程,并为该进程指定要执行的目标函数target_func,进程启动后将执行该函数
p.start() #启动进程 print('主进程运行')
print('主进程pid:',os.getpid())
运行输出结果>>>:
>>主进程运行
>>主进程pid: 2120
>>子进程运行
>>子进程pid: 7796
>>子进程的ppid 2120
PS:子进程被创建并启动,但子进程子print()函数并未立即执行,反而是主进程中的print()函数先执行。这说明进程间的执行顺序是不确定的,并非同步执行。
使用join()方法控制子进程执行顺序
__authon__ = 'tian' __date__ = '2019/12/26 22:54' import multiprocessing import os def target_func(): print('子进程运行') print('子进程pid:',os.getpid()) #os.getpid()获取进程的ID print('子进程的ppid',os.getppid())#os.getppid()获取进程的父进程ID,父进程是创建子进程的进程 if __name__ == '__main__': p = multiprocessing.Process(target=target_func)#创建进程,并为该进程指定要执行的目标函数target_func,进程启动后将执行该函数 p.start() #启动进程 p.join() print('主进程运行') print('主进程pid:',os.getpid()) 运行输出结果>>>: >>子进程运行 >>子进程pid: 3776 >>子进程的ppid 2936 >>主进程运行 >>主进程pid: 2936 PS:使用p.join()后主进程将等待子进程执行完成,然后再向下执行代码。
线程(Thread)
每一个进程默认有一个线程,这个线程被称为主线程。在主线程中创建其它线程来协助处理任务,这些线程也是并行运行的。
线程是进程的执行单元,CPU调度进程时,实际上是在进程的线程间作切换。另外线程间共享它们所有在进程的内存空间(栈除外)。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
def target_func(n):
for i in range(n):
print(i)
if __name__ == '__main__':
t = threading.Thread(target = target_func,args=(8,))
t.start()
print('主线程结束')
#输出 主线程和子线程交替执行
# 0
# 1
# 2
# 3
# 4主线程结束
#
# 5
# 6
# 7
使用join()让主线程等待子线程执行完成;
import threading
def target_func(n):
for i in range(n):
print(i)
if __name__ == '__main__':
t = threading.Thread(target = target_func,args=(8,))
t.start()
t.join() #主线程等待子线程执行完成
print('主线程结束')
#输出 主线程等子线程执行完成
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 主线程结束
线程锁
多个线程间共享进程的内存空间,如果多个线程同时修改和访问同一个对象,可能会出现非预期的错误。
例如:创建两个线程,这两个线程分别对number变量做一百万次+1操作
import threading
number = 0
lock = threading.Lock()
def add():
for i in range(1000000):
global number
lock.acquire() #加锁
number += 1
lock.release()#释放锁
if __name__ == '__main__':
t_1 = threading.Thread(target = add)
t_2 = threading.Thread(target = add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()
print(number)
这次结果完全正确,但是程序的执行速度变慢了,锁会带来性能上的损耗。
生产者消费者模式
利用多线程和队列可以实现生产者消费者模式。该模式通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度。
什么是生产者和消费者?
在线程世界里,生产者就是生产数据(或者说发布任务)的线程,消费者就是消费数据(或者说处理任务)的线程。在任务执行过程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者提供更多的任务,本质上这是一种供需不平衡的表现。为了解决这个问题,创造了生产者和消费模式。
生产者消费者模式的工作机制:
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不直接找生产者要数据,而是从阻塞队列里取,阻塞队列相当于一个缓冲区,平衡了生产者和消费者的处理能力,解耦了生产者和消费者。
生产者消费者模式的核心是【阻塞队列】也称消息队列。直接使用Python内置的queue模块中提供的队列就可以了。
下面是一个利用threading和queue模块,模拟一个简单的厨师做包子,顾客吃包子的例子;
#!/usr/bin/env python
# -*- coding:utf-8 -*-
__authon__ = 'tian'
__date__ = '2019/12/28 22:19'
import time
import queue
import threading
q = queue.Queue(10) #生成一个队列,用来保存【包子】最大数量为10
def productor(i):
#厨师不停的每2秒做一个包子
while True:
q.put("厨师{0}做的包子!".format(i))
time.sleep(2)
def consumer(j):
#顾客不停的每秒吃一个包子
while True:
print("顾客{0}吃了一个{1}".format(j,q.get()))
time.sleep(1)
#实例化了3个生产者(厨师)
for i in range(3):
t = threading.Thread(target = productor,args=(i,))
t.start()
#实例化了10个消费者(顾客)
for j in range(10):
v = threading.Thread(target=consumer,args=(j,))
v.start()
顾客0吃了一个厨师0做的包子!
顾客1吃了一个厨师1做的包子!
顾客2吃了一个厨师2做的包子!
顾客3吃了一个厨师0做的包子!
顾客4吃了一个厨师2做的包子!
顾客5吃了一个厨师1做的包子!
顾客6吃了一个厨师0做的包子!
顾客7吃了一个厨师1做的包子!
顾客8吃了一个厨师2做的包子!
....
....