并发编程
加入链接循环的套接字服务端
1 操作系统发展史
手工操作-穿孔卡片 >>> 批处理 >>> 多道程序系统 >>> 分时系统 >>> 通用操作系统
1 | 1 手工操作——穿孔卡片 |
2 进程基础
2.1 定义
1 | 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。 |
2.2 进程状态
在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
- 就绪(Ready)状态:当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
- 执行/运行(Running)状态当进程已获得处理机会,其程序正在处理机上执行,此时的进程状态称为执行状态。
- 阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
2.3 并发和并行
1 | 并发:你在跑步,鞋带开了,停下跑步,系鞋带,系完以后,继续跑步,在一个时间段内来看,你干了多个事 |
两者的区别
并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
3 进程高级
3.1 如何开启多进程
1 | from multiprocessing import Process |
3.2作业
1 | 1 写一个支持并发的TCP套接字服务端(可以有多个客户端同时跟服务端交互) |
3.3 进程调度算法(了解)
1 | -先来先服务调度算法 |
3.4 同步异步,阻塞非阻塞(了解)
- 同步和异步:关注的是消息通信机制
- 同步:在发起一个调用时,一直在等待结果返回
- 异步:在调用发起后,不会立刻得到结果,被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用
- 阻塞和非阻塞:关注的是等待调用结果时的状态
- 阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到调用结果后才会返回
- 非阻塞:非阻塞调用是指在不能立刻得到结果之前,该调用不会阻塞当前线程
- 例子
- 同步阻塞:打电话要买书,电话没挂,我也一直在等待,
- 同步非阻塞:打电话买书,电话没挂,我一边干别的事,一边听一下电话
- 异步阻塞:打电话买书,电话先挂掉,过一会老板会回回来(回调),老板给回电话的过程一直在等待
- 异步非阻塞:打电话买书,电话先挂掉,过一会老板会回回来(回调),老板给回电话的过程中,在干别的事
3.5 Process类
3.5.1 Process类的参数(重点)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22from multiprocessing import Process
def task(name,age):
print(name)
print(age)
if __name__ == '__main__':
# p=Process(target=task,args=['lqz',18])
# p=Process(target=task,kwargs={'age':19,'name':'lqz'},name='process01')
p=Process(target=task,kwargs={'age':19,'name':'lqz'})
p2=Process(target=task,kwargs={'age':19,'name':'lqz'})
p.start()
p2.start()
print(p.name)
print(p2.name)
# target=None, 你要执行的任务,函数
# name=None, 进程名
# args=(), 以位置参数给任务(函数)传递参数
# kwargs={} 以关键字的形式给任务(函数)传递参数3.5.2 Process类的方法,属性(重点)
- 1.方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from multiprocessing import Process
import time
def task(name,age):
time.sleep(10)
print(name)
print(age)
if __name__ == '__main__':
p=Process(target=task,kwargs={'age':19,'name':'lqz'})
p.start() #启动进程,必须调用start
# p.run() # 实际进程在执行的时候,执行的是run方法,但是调用run不会开进程,后面我们另一种开启进程的方案使用到它
# p.join() # 等待子进程执行完成
print(p.is_alive()) #True
p.terminate() # 杀死p这个进程,通知操作系统去杀死该进程
time.sleep(0.1)
print(p.is_alive()) #可能还是True
print('ssss')
print(p.is_alive()) #就是False
print(p)
'''
p.start() #启动进程,必须调用start
p.run() # 实际进程在执行的时候,执行的是run方法,但是调用run不会开进程,后面我们另一种开启进程的方案使用到它
p.join() # 等待子进程执行完成
p.terminate() # 杀死p这个进程,通知操作系统去杀死该进程,并不是立即结束
p.is_alive() #进程是否还存活
''' - 2.属性
1 | from multiprocessing import Process |
3.6 主进程和子进程的进程号
1 | from multiprocessing import Process |
3.7 同时开启多个进程
1 |
|
3.8 开启进程的另一种方案
1 | ### 通过继承Process类的方式来实现,重写run方法,run方法就是你要执行的任务,实例化得到对象,调用start方法开启进程 |
3.9 进程之间数据隔离
1 |
|
3.10 高并发的tcp服务端
1 | import socket |
3.11 进程同步(进程锁)(次重点)
1 | import time |
3.12 作业
1 | 作业: |
进程Queue、进程间通信、生产者消费者模型、线程概念
3.13 进程Queue介绍
1 | 1 进程间数据隔离,两个进程进行通信,借助于Queue |
3.13.1 基本使用
1 | from multiprocessing import Process,Queue |
3.13.2 通过Queue实现进程间通信
1 | from multiprocessing import Process,Queue |
3.13.3 批量生产数据放入Queue再批量取出
1 | from multiprocessing import Process,Queue |
3.13.4 生产者消费者模型(重点)
1 | from multiprocessing import Process, Queue |
3.13.5 多个生产者多个消费者的生产者消费者模型
1 | # 多个生产者在生产,多个消费者在消费 |
3.14 进程间数据共享(了解)
1 | from multiprocessing import Process,Manager,Lock |
4 线程
4.1 概念
1 | 如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二 |
4.2 如何开启线程
1 | from threading import Thread |
4.3 作业
1 | 1 基于进程写生产者消费者模型 |
4.4 进程和线程的区别
- 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
- 1.轻型实体
- 2.独立调度和分派的基本单位
4.6 使用线程的实际场景
开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
4.7 内存中的线程
4.8 GIL全局解释器锁
1 | #定义: |
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
1.设置 GIL;
2.切换到一个线程去运行;
3.运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
4.把线程设置为睡眠状态;
5.解锁 GIL;
6.再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
4.9 通过threading.Thread类创建线程
1 | # 创建线程的方式一 |
4.10 多线程和多进程的比较
4.10.1 pid的比较
1 | from threading import Thread |
4.10.2 开启效率的较量
1 | from threading import Thread |
4.10.3 内存数据的共享问题
1 | from threading import Thread |
4.11 Thread类的其他方法
Thread实例对象的方法:
isAlive()
:返回线程是否活动的。getName()
:返回线程名。setName()
:设置线程名。
threading模块提供的一些方法:
threading.currentThread()
:返回当前的线程变量。threading.enumerate()
:返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。threading.activeCount()
:返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
4.12 多线程实现socket
- 服务端
1 | import multiprocessing |
- 客户端
1 | import socket |
4.13 死锁问题(递归锁,可重入锁)
1 | 1 所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁 |
1 | from threading import Thread,Lock,RLock |
4.14 线程队列
1 | 1 线程Queue,解决线程间数据共享的问题 |
1 | from threading import Thread |
5 进程池,线程池
1 | # 线程池,进程池都在这个模块下concurrent.futures |
6 协程
6.1 介绍
1 | 1 协程是:程序级别的切换,单线程下实现并发 |
6.2 greenlet模块(初级模块,实现了保存状态加切换)
1 | # 安装第三方模块:在命令行下 |
6.3 gevent模块
1 | ## gevent模块,协程模块,遇到io可以自动切换 |
6.4 asyncio
1 | # 内置模块 python 3.4 推出这个模块,python作者主导的 |
作业
1 | 1 使用greenlet写两个task,一个计算从1+1w,另一个计算从1乘以到1w,统计一下,切换执行时间快还是不切换快 |