本节内容:

  1. 操作系统发展史介绍
  2. 进程、与线程区别
  3. python GIL全局解释器锁
  4. 线程
    1. 语法
    2. join、Daeman
    3. 线程锁之Lock\Rlock\信号量
    4. Event事件 
    5. queue队列
    6. 生产者消费者模型
  5. 进程
    1. 语法
    2. 进程间通讯
    3. 进程池 
  6. 协程

1.操作系统发展史

手工操作(无操作系统)

1946年第一台计算机诞生--20世纪50年代中期,还未出现操作系统,计算机工作采用手工操作方式。

手工操作
程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。

手工操作方式两个特点:
(1)用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
(2)CPU 等待手工操作。CPU的利用不充分。

20世纪50年代后期,出现人机矛盾:手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾,手工操作方式已严重损害了系统资源的利用率(使资源利用率降为百分之几,甚至更低),不能容忍。唯一的解决办法:只有摆脱人的手工操作,实现作业的自动过渡。这样就出现了成批处理。

批处理系统

批处理系统:加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(这作业包括程序、数据和命令)。

联机批处理系统
首先出现的是联机批处理系统,即作业的输入/输出由CPU来处理。
主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,并按上述步骤重复处理。

监督程序不停地处理各个作业,从而实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,有效克服了人机矛盾,提高了计算机的利用率。

但是,在作业输入和结果输出时,主机的高速CPU仍处于空闲状态,等待慢速的输入/输出设备完成工作: 主机处于“忙等”状态。

脱机批处理系统
为克服与缓解高速主机与慢速外设的矛盾,提高CPU的利用率,又引入了脱机批处理系统,即输入/输出脱离主机控制。
这种方式的显著特征是:增加一台不与主机直接相连而专门用于与输入/输出设备打交道的卫星机。
其功能是:
(1)从输入机上读取用户作业并放到输入磁带上。
(2)从输出磁带上读取执行结果并传给输出机。

这样,主机不是直接与慢速的输入/输出设备打交道,而是与速度相对较快的磁带机发生关系,有效缓解了主机与设备的矛盾。主机与卫星机可并行工作,二者分工明确,可以充分发挥主机的高速计算能力。

脱机批处理系统:20世纪60年代应用十分广泛,它极大缓解了人机矛盾及主机与外设的矛盾。IBM-7090/7094:配备的监督程序就是脱机批处理系统,是现代操作系统的原型。

不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。

为改善CPU的利用率,又引入了多道程序系统。

多道程序系统

多道程序设计技术

所谓多道程序设计技术,就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即转去运行另一道程序。

单道程序的运行过程:
在A程序计算时,I/O空闲, A程序I/O操作时,CPU空闲(B程序也是同样);必须A工作完成后,B才能进入内存中开始工作,两者是串行的,全部完成共需时间=T1+T2。

多道程序的运行过程:
将A、B两道程序同时存放在内存中,它们在系统的控制下,可相互穿插、交替地在CPU上运行:当A程序因请求I/O操作而放弃CPU时,B程序就可占用CPU运行,这样 CPU不再空闲,而正进行A I/O操作的I/O设备也不空闲,显然,CPU和I/O设备都处于“忙”状态,大大提高了资源的利用率,从而也提高了系统的效率,A、B全部完成所需时间<<T1+T2。

多道程序设计技术不仅使CPU得到充分利用,同时改善I/O设备和内存的利用率,从而提高了整个系统的资源利用率和系统吞吐量(单位时间内处理作业(程序)的个数),最终提高了整个系统的效率。

单处理机系统中多道程序运行时的特点:
(1)多道:计算机内存中同时存放几道相互独立的程序;
(2)宏观上并行:同时进入系统的几道程序都处于运行过程中,即它们先后开始了各自的运行,但都未运行完毕;
(3)微观上串行:实际上,各道程序轮流地用CPU,并交替运行。

多道程序系统的出现,标志着操作系统渐趋成熟的阶段,先后出现了作业调度管理、处理机管理、存储器管理、外部设备管理、文件系统管理等功能。

多道批处理系统
20世纪60年代中期,在前述的批处理系统中,引入多道程序设计技术后形成多道批处理系统(简称:批处理系统)。
它有两个特点:
(1)多道:系统内可同时容纳多个作业。这些作业放在外存中,组成一个后备队列,系统按一定的调度原则每次从后备作业队列中选取一个或多个作业进入内存运行,运行作业结束、退出运行和后备作业进入运行均由系统自动实现,从而在系统中形成一个自动转接的、连续的作业流。
(2)成批:在系统运行过程中,不允许用户与其作业发生交互作用,即:作业一旦进入系统,用户就不能直接干预其作业的运行。

批处理系统的追求目标:提高系统资源利用率和系统吞吐量,以及作业流程的自动化。

批处理系统的一个重要缺点:不提供人机交互能力,给用户使用计算机带来不便。
虽然用户独占全机资源,并且直接控制程序的运行,可以随时了解程序运行情况。但这种工作方式因独占全机造成资源效率极低。

一种新的追求目标:既能保证计算机效率,又能方便用户使用计算机。 20世纪60年代中期,计算机技术和软件技术的发展使这种追求成为可能。

分时系统

由于CPU速度不断提高和采用分时技术,一台计算机可同时连接多个用户终端,而每个用户可在自己的终端上联机使用计算机,好象自己独占机器一样。

分时技术:把处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用。

若某个作业在分配给它的时间片内不能完成其计算,则该作业暂时中断,把处理机让给另一作业使用,等待下一轮时再继续其运行。由于计算机速度很快,作业运行轮转得很快,给每个用户的印象是,好象他独占了一台计算机。而每个用户可以通过自己的终端向系统发出各种操作控制命令,在充分的人机交互情况下,完成作业的运行。

具有上述特征的计算机系统称为分时系统,它允许多个用户同时联机使用计算机。

特点:
(1)多路性。若干个用户同时使用一台计算机。微观上看是各用户轮流使用计算机;宏观上看是各用户并行工作。
(2)交互性。用户可根据系统对请求的响应结果,进一步向系统提出新的请求。这种能使用户与系统进行人机对话的工作方式,明显地有别于批处理系统,因而,分时系统又被称为交互式系统。
(3)独立性。用户之间可以相互独立操作,互不干扰。系统保证各用户程序运行的完整性,不会发生相互混淆或破坏现象。
(4)及时性。系统可对用户的输入及时作出响应。分时系统性能的主要指标之一是响应时间,它是指:从终端发出命令到系统予以应答所需的时间。

分时系统的主要目标:对用户响应的及时性,即不至于用户等待每一个命令的处理时间过长。

分时系统可以同时接纳数十个甚至上百个用户,由于内存空间有限,往往采用对换(又称交换)方式的存储方法。即将未“轮到”的作业放入磁盘,一旦“轮到”,再将其调入内存;而时间片用完后,又将作业存回磁盘(俗称“滚进”、“滚出“法),使同一存储区域轮流为多个用户服务。

多用户分时系统是当今计算机操作系统中最普遍使用的一类操作系统。

实时系统

虽然多道批处理系统和分时系统能获得较令人满意的资源利用率和系统响应时间,但却不能满足实时控制与实时信息处理两个应用领域的需求。于是就产生了实时系统,即系统能够及时响应随机发生的外部事件,并在严格的时间范围内完成对该事件的处理。
实时系统在一个特定的应用中常作为一种控制设备来使用。

实时系统可分成两类:
(1)实时控制系统。当用于飞机飞行、导弹发射等的自动控制时,要求计算机能尽快处理测量系统测得的数据,及时地对飞机或导弹进行控制,或将有关信息通过显示终端提供给决策人员。当用于轧钢、石化等工业生产过程控制时,也要求计算机能及时处理由各类传感器送来的数据,然后控制相应的执行机构。
(2)实时信息处理系统。当用于预定飞机票、查询有关航班、航线、票价等事宜时,或当用于银行系统、情报检索系统时,都要求计算机能对终端设备发来的服务请求及时予以正确的回答。此类对响应及时性的要求稍弱于第一类。

实时操作系统的主要特点:
(1)及时响应。每一个信息接收、分析处理和发送的过程必须在严格的时间限制内完成。
(2)高可靠性。需采取冗余措施,双机系统前后台工作,也包括必要的保密措施等。

操作系统发展图谱

2.进程和线程的区别

进程(process)

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

线程(thread)

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

有了进程为什么还要线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。

再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!

区别:

1.线程共享内存空间,进程是独立的内存
2.线程可直接访问它的进程数据片段,可修改它的进程,进程则是从父进程那赋值数据片段,不可修改父进程
3.同一进程的线程可以直接交互,而进程之间通讯必须通过一个中间代理
4.新线程很容易创建,但新进程需要对其父进程进行一次克隆
5.一个线程可以操作同一进程的其他线程,但是进程只能操作子进程
6.修改主线程有可能影响到其他线程的行为,但修改父进程不会影响子进程

NOTE:线程启动速度快,进程启动速度慢,但他们运行快慢是没有可比性的

3.Python GIL(Global Interpreter Lock)  

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

4.线程

(1)语法

线程有2种调用方式,如下:

直接调用

 1 import threading
 2 import time
 3
 4 def sayhi(num): #定义每个线程要运行的函数
 5
 6     print("running on number:%s" %num)
 7
 8     time.sleep(3)
 9
10 if __name__ == '__main__':
11
12     t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
13     t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
14
15     t1.start() #启动线程
16     t2.start() #启动另一个线程
17
18     print(t1.getName()) #获取线程名
19     print(t2.getName())

继承式调用

import threading
import timeclass MyThread(threading.Thread):def __init__(self,num):threading.Thread.__init__(self)self.num = numdef run(self):#定义每个线程要运行的函数print("running on number:%s" %self.num)time.sleep(3)if __name__ == '__main__':t1 = MyThread(1)t2 = MyThread(2)t1.start()t2.start()

(2)Join & Daemon

 1 import time
 2 import threading
 3
 4
 5 def run(n):
 6     print('[%s]------running----\n' % n)
 7     time.sleep(2)
 8     print('--done--')
 9
10
11 def main():
12     for i in range(5):
13         t = threading.Thread(target=run, args=[i, ])
14         t.start()
15         t.join(1)
16         print('starting thread', t.getName())
17
18
19 m = threading.Thread(target=main, args=[])
20 m.setDaemon(True)  # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务
21 m.start()
22 m.join(timeout=2)  #超时设置为2s,过了这时间,主线程不再等待,直接结束
23 print("---main thread done----")

(3)线程锁(互斥锁Mutex)

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?

不加锁:

import time
import threadingdef addNum():global num #在每个线程中都获取这个全局变量print('--get num:',num )time.sleep(1)num  -=1 #对此公共变量进行-1操作

num = 100  #设定一个共享变量
thread_list = []
for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list: #等待所有线程执行完毕
    t.join()print('final num:', num )

正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。

*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁

加锁版本:

import time
import threadingdef addNum():global num  # 在每个线程中都获取这个全局变量print('--get num:', num)time.sleep(1)lock.acquire()  # 修改数据前加锁num -= 1  # 对此公共变量进行-1操作lock.release()  # 修改后释放
num = 100  # 设定一个共享变量
thread_list = []
lock = threading.Lock()  # 生成全局锁
for i in range(100):t = threading.Thread(target=addNum)t.start()thread_list.append(t)for t in thread_list:  # 等待所有线程执行完毕
    t.join()print('final num:', num)

GIL VS Lock 

机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了

RLock(递归锁)

说白了就是在一个大锁中还要再包含子锁

 1 import threading,time
 2
 3 def run1():
 4     print("grab the first part data")
 5     lock.acquire()
 6     global num
 7     num +=1
 8     lock.release()
 9     return num
10 def run2():
11     print("grab the second part data")
12     lock.acquire()
13     global  num2
14     num2+=1
15     lock.release()
16     return num2
17 def run3():
18     lock.acquire()
19     res = run1()
20     print('--------between run1 and run2-----')
21     res2 = run2()
22     lock.release()
23     print(res,res2)
24
25
26 if __name__ == '__main__':
27
28     num,num2 = 0,0
29     lock = threading.RLock()
30     for i in range(10):
31         t = threading.Thread(target=run3)
32         t.start()
33
34 while threading.active_count() != 1:
35     print(threading.active_count())
36 else:
37     print('----all threads done---')
38     print(num,num2)

Semaphore(信号量)

互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

import threading, timedef run(n):semaphore.acquire()time.sleep(1)print("run the thread: %s\n" % n)semaphore.release()if __name__ == '__main__':# num = 0semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行for i in range(20):t = threading.Thread(target=run, args=(i,))t.start()while threading.active_count() != 1:pass  # print (threading.active_count())
else:print('----all threads done---')# print(num)

(4)事件(event)

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

  • clear:将“Flag”设置为False
  • set:将“Flag”设置为True

通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。

 1 import time
 2 import threading
 3
 4 event = threading.Event()
 5
 6 def lighte():
 7     count = 0 # set flag
 8     event.set() #开始设置为绿灯
 9     while True:
10         if count >5 and count <10:  #红灯来,清空标志位
11             event.clear()
12             print("\033[41;1m red light is on...\033[0m")
13         elif count >10:             #绿灯来,设置标志位
14             event.set()
15             count = 0
16         else:
17             print("\033[42;1m green light is on...\033[0m")
18
19         time.sleep(1)     #等一秒
20         count +=1
21
22 def car(name):
23     while True:
24         if event.is_set(): #检测是否设置标志位,设置为绿灯,没设置为红灯
25             print("[%s] is running.."%name)
26             time.sleep(1)      #避免次线程执行过快,灯来不及跳,一直占住cpu
27         else:
28             print("[%s] is waiting red light.."%name)
29             event.wait()    #标志位清空,为阻塞状态
30             print("\033[34;1m green light is on ,[%s] start going..\033[0m"%name)
31
32 light = threading.Thread(target=lighte,)
33 light.start()
34
35 car1  = threading.Thread(target= car,args=('LEUX',))
36 car1.start()

条件(Condition)

使得线程等待,只有满足某条件时,才释放n个线程

 1 import threading
 2
 3 def run(n):
 4     con.acquire()  #开启条件
 5     con.wait()
 6     print("run the thread: %s" %n)
 7     con.release() #释放条件
 8
 9 if __name__ == '__main__':
10
11     con = threading.Condition()  #设置条件
12     for i in range(10):
13         t = threading.Thread(target=run, args=(i,))
14         t.start()
15
16     while True:
17         inp = input('>>>')
18         if inp == 'q':
19             break
20         con.acquire()
21         con.notify(int(inp))
22         con.release()

输入2,则打印2个运行结果,再接着输入3,在打印后面的3各运行结果。。。

Timer

定时器,指定n秒后执行某操作

1 from threading import Timer
2
3
4 def hello():
5     print("hello, world")
6
7 t = Timer(1, hello)
8 t.start()  # after 1 seconds, "hello, world" will be printed

开始执行1秒后才打印结果。

(5)queue队列

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

class queue.Queue(maxsize=0) #先入先出
class queue.LifoQueue(maxsize=0) #last in fisrt out class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

Constructor for a priority queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.

The lowest valued entries are retrieved first (the lowest valued entry is the one returned by sorted(list(entries))[0]). A typical pattern for entries is a tuple in the form: (priority_number, data).

exception queue.Empty

Exception raised when non-blocking get() (or get_nowait()) is called on a Queue object which is empty.

exception queue.Full

Exception raised when non-blocking put() (or put_nowait()) is called on a Queue object which is full.

Queue.qsize()
Queue.empty() #return True if empty  
Queue.full() # return True if full 
Queue.put(itemblock=Truetimeout=None)

Put item into the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case).

Queue.put_nowait(item)

Equivalent to put(item, False).

Queue.get(block=Truetimeout=None)

Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise the Empty exception (timeout is ignored in that case).

Queue.get_nowait()

Equivalent to get(False).

Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.

Queue.task_done()

Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.

If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).

Raises a ValueError if called more times than there were items placed in the queue.

Queue.join() block直到queue被消费完毕

(6)生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

 1 import threading
 2 import queue,time
 3
 4
 5 def producer():
 6     count = 1
 7     while True:
 8
 9         q.put("骨头%s"%count)
10         print("生产了骨头%s"%count)
11         count +=1
12         time.sleep(0.1)
13
14 def consumer(n):
15     while True:
16         print("%s 取到 %s" %( n, q.get()))
17         time.sleep(1)
18
19 q = queue.Queue(maxsize=10)
20
21 p = threading.Thread(target=producer, )
22 c = threading.Thread(target=consumer,args= ('wq',))
23 c1 = threading.Thread(target=consumer,args= ('lw',))
24
25 p.start()
26 c.start()
27 c1.start()

5.进程

(1)语法

 1 from multiprocessing import Process
 2 import os
 3
 4
 5 def info(title):
 6     print(title)
 7     print('module name:', __name__)
 8     print('parent process:', os.getppid())
 9     print('process id:', os.getpid())
10     print("\n\n")
11
12
13 def f(name):
14     info('\033[31;1mfunction f\033[0m')
15     print('hello', name)
16
17
18 if __name__ == '__main__':
19     info('\033[32;1mmain process line\033[0m')
20     p = Process(target=f, args=('bob',))
21     p.start()
22     p.join()

输出结果:

(2)进程间通讯

不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

Queues

使用方法跟threading里的queue差不多

 1 from multiprocessing import Process,Queue
 2
 3 def f(qq):
 4     qq.put([1,2,3])
 5
 6 if __name__ == '__main__':
 7     q = Queue()
 8     p = Process(target=f,args=(q,)) #子进程中放数据
 9     p.start()
10     print(q.get()) #主进程中提取数据

Pipes

 1 from multiprocessing import Pipe,Process
 2
 3 def f(conn):
 4     conn.send([1,2,3])
 5     conn.send(['a','s','d'])
 6     print("from parent:",conn.recv())
 7
 8 if __name__ == '__main__':
 9     parent_conn,child_conn = Pipe()  #管道有两端,从一端放数据,从另一端取
10     p = Process(target=f,args=(child_conn,))
11     p.start()
12     print(parent_conn.recv())
13     print(parent_conn.recv())
14     parent_conn.send("hello son!")

输出结果:

[1, 2, 3]
['a', 's', 'd']
from parent: hello son!

Managers

from multiprocessing import Process,Manager
import osdef f(dic,l):dic [os.getpid()] = os.getpid()  #将进程号存入字典
l.append(os.getpid()) #将进程号存入列表print(l)if __name__ == '__main__':with Manager() as manager:d = manager.dict()  #生成一个字典,用在共享进程间数据l = manager.list(range(3))  #生成一个列表,共享进程间数据p_list = []  #存放进程for i in range(5):p = Process(target=f , args=(d,l,))p.start()p_list.append(p)for res in p_list:res.join()print(d)print(l)

输出结果:

[0, 1, 2, 3816]
[0, 1, 2, 3816, 9496]
[0, 1, 2, 3816, 9496, 8740]
[0, 1, 2, 3816, 9496, 8740, 7052]
[0, 1, 2, 3816, 9496, 8740, 7052, 5812]
{3816: 3816, 9496: 9496, 7052: 7052, 5812: 5812, 8740: 8740}
[0, 1, 2, 3816, 9496, 8740, 7052, 5812]

(3)进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

  • apply
  • apply_async
 1 from multiprocessing import Process,Pool
 2 import  os,time
 3
 4
 5
 6 def foo(i):
 7     time.sleep(2)
 8     print("in process:",os.getpid())
 9     return i+100
10
11 def Bar (arg):
12     print("\033[34;1min the bar:\033[0m",arg)
13
14 if __name__ == '__main__':
15     pool = Pool(5)
16     print("main process:",os.getpid())
17     for i in range(10):
18         pool.apply_async(func=foo,args=(i,),callback=Bar)#将foo返回的结果传给回调函数Bar
19
20     print("end")
21     pool.close()       #一定要先关闭,再等待
22     pool.join()

输出结果:

main process: 2412
end
in process: 5852
in process: 5284
in the bar: 100
in the bar: 101
in process: 9356
in process: 2364
in the bar: 102
in the bar: 103
in process: 6340
in the bar: 104
in process: 5284
in process: 5852
in the bar: 106
in the bar: 105
in process: 2364
in process: 9356
in the bar: 108
in the bar: 107
in process: 6340
in the bar: 109

6.协程

协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

协程的好处:

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
    •   "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

使用yield实现协程操作例子

 1 import time
 2
 3
 4 def consumer(name):
 5     print("--->starting eating baozi...")
 6     while True:
 7         new_baozi = yield
 8         print("[%s] is eating baozi %s" % (name, new_baozi))
 9         time.sleep(1)
10
11
12 def producer():
13     r = con.__next__()
14     r = con2.__next__()
15     n = 0
16     while n < 5:
17         n += 1
18         con.send(n)
19         con2.send(n)
20         print("\033[32;1m[producer]\033[0m is making baozi %s" % n)
21
22
23 if __name__ == '__main__':
24     con = consumer("c1")
25     con2 = consumer("c2")
26     p = producer()

看楼上的例子,我问你这算不算做是协程呢?你说,我他妈哪知道呀,你前面说了一堆废话,但是并没告诉我协程的标准形态呀,我腚眼一想,觉得你说也对,那好,我们先给协程一个标准定义,即符合什么条件就能称之为协程:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里自己保存多个控制流的上下文栈
  4. 一个协程遇到IO操作自动切换到其它协程

基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的线程,因为它有一点功能没实现,哪一点呢?

Greenlet

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator。

from greenlet import greenletdef test1():print(12)gr2.switch()print(34)gr2.switch()def test2():print(56)gr1.switch()print(78)gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

输出结果:

12
56
34
78

Gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

 1 import gevent
 2
 3
 4 def func1():
 5     print('\033[31;1m李闯在跟海涛搞...\033[0m')
 6     gevent.sleep(2)  #遇到IO自动切换
 7     print('\033[31;1m李闯又回去跟继续跟海涛搞...\033[0m')
 8
 9
10 def func2():
11     print('\033[32;1m李闯切换到了跟海龙搞...\033[0m')
12     gevent.sleep(1)  #遇到IO自动切换
13     print('\033[32;1m李闯搞完了海涛,回来继续跟海龙搞...\033[0m')
14
15
16 gevent.joinall([
17     gevent.spawn(func1),
18     gevent.spawn(func2),
19     # gevent.spawn(func3),
20 ])

输出结果:

李闯在跟海涛搞...
李闯切换到了跟海龙搞...
李闯搞完了海涛,回来继续跟海龙搞...
李闯又回去跟继续跟海涛搞...

通过gevent实现单线程下的多socket并发

server side 

 1 import sys
 2 import socket
 3 import time
 4 import gevent
 5
 6 from gevent import socket,monkey
 7 monkey.patch_all()
 8
 9
10 def server(port):
11     s = socket.socket()
12     s.bind(('0.0.0.0', port))
13     s.listen(500)
14     while True:
15         cli, addr = s.accept()
16         gevent.spawn(handle_request, cli)
17
18
19
20 def handle_request(conn):
21     try:
22         while True:
23             data = conn.recv(1024)
24             print("recv:", data)
25             conn.send(data)
26             if not data:
27                 conn.shutdown(socket.SHUT_WR)
28
29     except Exception as  ex:
30         print(ex)
31     finally:
32         conn.close()
33 if __name__ == '__main__':
34     server(8001)

View Code

client side  

 1 mport socket
 2
 3 HOST = 'localhost'    # The remote host
 4 PORT = 8001           # The same port as used by the server
 5 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 6 s.connect((HOST, PORT))
 7 while True:
 8     msg = bytes(input(">>:"),encoding="utf8")
 9     s.sendall(msg)
10     data = s.recv(1024)
11     #print(data)
12
13     print('Received', repr(data))
14 s.close()

View Code

转载于:https://www.cnblogs.com/Ironboy/p/7305731.html

Day8—进程,线程,协程相关推荐

  1. 简要说明__python3中的进程/线程/协程

    多任务可以充分利用系统资源,极大提升程序运行效率,多任务的实现往往与 多线程,多进程,多协程有关 稳定性: 进程 > 线程 > 协程 系统资源占用量:进程 > 线程 > 协程 ...

  2. Linux的进程/线程/协程系列4:进程知识深入总结:上篇

    Linux的进程/线程/协程系列4:进程/线程相关知识总结 前言 本篇摘要: 1. 进程基础知识 1.1 串行/并行与并发 1.2 临界资源与共享资源 1.3 同步/异步与互斥 1.4 进程控制原语 ...

  3. linux的进程/线程/协程系列5:协程的发展复兴与实现现状

    协程的发展复兴与实现现状 前言 本篇摘要: 1. 协同制的发展史 1.1 协同工作制的提出 1.2 自顶向下,无需协同 1.3 协同式思想的应用 2. 协程的复兴 2.1 高并发带来的问题 2.2 制 ...

  4. linux的进程/线程/协程系列3:查看linux内核源码——vim+ctags/find+grep

    linux的进程/线程/协程系列3:查看linux内核源码--vim+ctags/find+grep 前言 摘要: 1. 下载linux内核源码 2. 打标签方法:vim+ctags 2.1 安装vi ...

  5. linux的进程/线程/协程系列1:进程到协程的演化

    linux的进程/线程/协程系列1:进程到协程的演化 前言 摘要: 1. 一些历史:批处理时代 2. 现代操作系统启动过程 3. 进程(process)的出现 4. 线程(thread)与线程池 5. ...

  6. Python之进程+线程+协程(异步、selectors模块、阻塞、非阻塞IO)

    文章目录 一.IO多路复用 二.selectors模块 本篇文字是关于IO多路复用的更深入一步的总结,上一篇 Python之进程+线程+协程(事件驱动模型.IO多路复用.select与epoll)对I ...

  7. 进程 线程 协程 各自的概念以及三者的对比分析

    文章目录 1 进程 2 线程 3 进程和线程的区别和联系 3.1 区别 3.2 联系 4 举例说明进程和线程的区别 5 进程/线程之间的亲缘性 6 协程 线程(执行一个函数)和协程的区别和联系 协程和 ...

  8. 进程 线程 协程_进程,线程,协程那些事

    无论我们写出怎样的程序,最后都是由操作系统来运行我们的程序,而操作系统如何管理我们的程序,我们程序的数据如何保存和计算,这些都是操作系统需要处理的事情,我们只要将写好的程序交给操作系统就好. 虽然操作 ...

  9. python进程线程协程区别_Python3多线程与协程

    python中的多线程非常的常用,之前一直糊里糊涂地使用,没有一些系统性的概念,记录一下~ 0x001 多线程的优势:可将长时间占用的程序放到后台 可能会加速程序执行速度 能够实现一些类似同步执行的效 ...

  10. Go进程/线程/协程:单元 空间资源 切换 共享

    https://www.imooc.com/article/31751 进程process 线程 thread   协程goroutine 操作系统分为操作与资源两部分,操作就是方法,资源就是硬盘资源 ...

最新文章

  1. 微信小程序绑定数据以及自定义指令
  2. compiz把xfce4系统搞崩溃后的恢复方案
  3. 美团点评DBProxy读写分离使用说明
  4. UVALive 6885 Flowery Trails 最短路枚举
  5. 《计算机应用基础》模拟试卷三,2015年《计算机应用基础》模拟试题及答案(一)...
  6. 手机上网有几种方式?
  7. Python 打包工具cx_freeze 问题记录及解决办法
  8. 通用业务平台设计(一):概览
  9. Qt程序上线崩溃,处理方式(附微软编译器命令)
  10. 基于Springboot+vue的办公OA系统#毕业设计
  11. html5分辨率异常自动检测
  12. 《论英语能力从小开始培养的重要性,不一定是非要去国外》
  13. 查看gup服务器内核信息,国内免费gpu服务器试用
  14. 96微信编辑器html在哪里,96微信编辑器怎么把文章生成链接?
  15. 一文助你入门HTML(❤ ω ❤)
  16. 淘宝API获取——商品详情信息、DESC信息、主图
  17. 《从1到N企业数字化生存指南》读书笔记
  18. Panabit专业流量监控开源软件
  19. 淘宝网 286亿海量图片存储与处理架构
  20. [kuanbin带我飞]的专题1---简单搜索

热门文章

  1. echarts图形铺满容器
  2. win10如何手动强制关联默认文件打开方式应用
  3. 详解六种常见的上下文切换场景
  4. 为什么戏说php,戏说PHP——1.1切的开始
  5. python主程序退出方法quit()
  6. 毕业设计-基于微信小程序的校园参赛系统
  7. MUR1660AC-ASEMI高压大电流快恢复二极管
  8. python安装报_ssl问题
  9. 拍拍抢拍精灵V2.1正式版--腾讯拍拍秒杀器
  10. 第一周预习HTML标签(笔记可以不记,代码一定要敲)