1. 多任务的概念
多任务简单地说,就是操作系统可以同时运行多个任务。分为并行和并发两种。
1.1 并行
指的是任务数小于等于CPU核数,即任务真的是一起执行的
1.2 并发
指的是任务数多于CPU核数,通过操作系统的各种任务调度算法,实现用多个任务一起执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
1.3 网络的概念
网络就是辅助双方或多方,连接在一起的辅助工具
1.4 网络的作用
网络能够实现数据的相互传送,能够通信,互相传递信息等。
1.5 ip地址
ip地址就是在网络中标记一台计算机的地址,比如192.168.1.1;在本地局域网上是唯一的。
在这么多网络IP中,国际规定有一部分IP地址是用于我们的局域网使用,也就
是属于私网IP,不在公网中使用的,它们的范围是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
复制代码
注意
IP地址127.0.0.1~127.255.255.255用于回路测试,
如:127.0.0.1可以代表本机IP地址,用http://127.0.0.1就可以测试本机中配置的Web服务器。
1.6 端口(port)
端口号就是电脑上某个程序对应的地址,用来标记某个程序,端口号只有整数,范围是从0到65535
知名端口:知名端口是众所周知的端口号,范围从0到1023,都已被占用
动态端口:动态端口的范围是从1024到65535,之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。
1.5 socket
socket是一种进程之间的通信方式,简称套接字。它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的
例如我们每天浏览网页、QQ 聊天、收发 email 等等。
创建socket
import socket
socket.socket(AddressFamily, Type)
复制代码
说明:
函数 socket.socket 创建一个 socket,该函数带有两个参数:
Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
2. 线程
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用
2.1 多线程执行
#coding=utf-8
import threading
import time
def saySorry():
print("我能吃饭了吗?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=saySorry)
t.start() #启动线程,即让线程开始执行
复制代码使用的是多线程并发的操作,当调用start()时,才会真正的创建线程,并且开始执行
主线程结束后,子进程立即结束
2.2 线程执行代码的封装
python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
2.3 线程的执行顺序
多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞,到sleep结束后,线程进入就绪状态,等待调度。而线程调度将自行选择一个线程执行。
2.4 多线程共享全局变量
在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
缺点就是,线程是对全局变量随意更改可能造成多线程之间对全局变量的混乱
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确
2.5 互斥锁
当多个线程几乎同时修改某个共享数据的时候,需要进行同步控制。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的处理锁定:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
复制代码如果这个锁之前没有上锁的,那么acquire不会堵塞
如果在调用acquire对这个锁上锁之前它已经被其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
2.6 死锁
在线程键共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
可以添加超时时间等,解决死锁
案例:多线程版udp聊天器
import socket
import threading
def send_msg(udp_socket):
"""获取键盘数据,并将其发送给对方"""
while True:
# 1. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
# 2. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 3. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
# 4. 发送数据
udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
def recv_msg(udp_socket):
"""接收数据并显示"""
while True:
# 1. 接收数据
recv_msg = udp_socket.recvfrom(1024)
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print(">>>%s:%s" % (str(recv_ip), recv_msg))
def main():
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(("", 7890))
# 3. 创建一个子线程用来接收数据
t = threading.Thread(target=recv_msg, args=(udp_socket,))
t.start()
# 4. 让主线程用来检测键盘数据并且发送
send_msg(udp_socket)
if __name__ == "__main__":
main()
复制代码
3. 进程
multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情。
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动
# -*- coding:utf-8 -*-
from multiprocessing import Process
import os
import time
def run_proc():
"""子进程要执行的代码"""
print('子进程运行中,pid=%d...' % os.getpid()) # os.getpid获取当前进程的进程号
print('子进程将要结束...')
if __name__ == '__main__':
print('父进程pid: %d' % os.getpid()) # os.getpid获取当前进程的进程号
p = Process(target=run_proc)
p.start()
复制代码
3.1 process语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
target:如果传递了函数的引用,可以任务这个子进程就执行这里的代码
args:给target指定的函数传递的参数,以元组的方式传递
kwargs:给target指定的函数传递命名参数
name:给进程设定一个名字,可以不设定
group:指定进程组,大多数情况下用不到
Process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
is_alive():判断进程子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
pid:当前进程的pid(进程号)
进程之间不共享全局变量
3.2 进程与线程的区别
进程是系统进行资源分配和调度的一个独立单位
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其它线程共享进程所拥有的全部资源
一个程序至少有一个进程,一个进程至少有一个线程
线程的划分尺度小于进程,使得多线程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
线程不能独立执行,必须依存在进程中
可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人
线程和进程在使用上各有优缺点:线程执行开销小。但不利于资源的管理和保护,而进程正相反。
3. 进程池Pool
当需要创建的子进程数量巨大时,就可以用到multiprocessing模块提供的Pool方法
apply_async(func[,args[,kwds]]):使用非阻塞方式调用func,(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表
close():关闭Pool,使其不再接收新的任务
terminate():不管任务是否完成,立即终止
join():主进程阻塞,等待子进程的退出,必须在close或terminate之后使用
在程序执行过程中,关闭进程池,则程序会立即停止,不会再继续执行后续语句。
4. Queue
进程是通过导入Queue,用Queue实现多进程之间的数据传递,Queue本身是一个消息队列程序。
Queue.qsize():返回当前队列包含的消息数量
Queue.empty():如果队列为空,返回True,反之False
Queue.full():如果队列满了,返回True,反之False
Queue.get([block[,timeout]]):获取队列中的一条消息,然后将其从队列中移除,block默认值为True
Queue.put_nowait(item):相当Queue.put(item,False)
5. 迭代器
5.1 可迭代对象
我们把可以通过for...in...这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)**
可迭代对象的本质就是可以向我们提供一个这样的中间"人"即迭代器帮助我们对其进行迭代遍历使用。
可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据,也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象。
5.2 如何判断一个对象是否可以迭代
可以使用isinstance()判断一个对象是否是iterable对象
生成器是一类特殊的迭代器
5.3 创建生成器的方法
只要把一个列表生成式的[]改成()
只要在def中有yield关键字的就称为生成器,使用了yield关键字的函数不再是函数,而是生成器
5.4 唤醒
除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒来执行,使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据
6. gevent
6.1 geven使用
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(),i)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
复制代码
运行结果
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
复制代码
3个greenlet是依次运行而不是交替运行,可以导入time,添加延时切换执行
6.2 线程、进程、协程对比
进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很大,效率很低
线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
协程切换任务资源很小,效率高
多进程、多线程根据CPU核数不一样可能是并行的,但是协程是在一个线程中所以是并发
7. 阻塞和非阻塞
Python中遇到recv、accept、等语句时,是默认阻塞的,即如果不设置一些条件时,程序会一直等待下去。
而非阻塞就是通过设置条件,把阻塞变为非阻塞,所以转变语法需要在阻塞之前设置
例:server_socket.setblocking(False)
复制代码
8. Http协议
8.1 浏览器向服务器发送http请求
请求包括:请求头、请求体、请求行
8.2 服务器向浏览器返回HTTP响应
响应包括:响应头、响应行、响应体
9 长连接和短连接
9.1 短连接
短连接就是建立连接、接收数据、关闭连接,每次只获取1次数据
9.2 长连接
长连接就是建立连接后,多次请求,直到没有请求后关闭的连接
代码最后有close操作的其实都是短连接,长连接不能在连接中强制调用close
10. 三次握手,四次挥手
TCP通信的整个过程
三次握手:建立连接时,客户端向服务器发送连接请求,服务器向客户端回应请求的同时向客户端发送连接请求,客户端回应请求,服务器收到时,三次握手成功,双方连接成功。
四次挥手:客户端调用close时,向服务器发送请求,服务器回应请求同时解堵塞,调用自己close后,再次向客户端发送close请求,此时客户端方会等待两个最大报文时间,等待接收服务器的请求(等待是为了避免断网,断电等特殊情况),收到服务器的请求后,向服务器回应请求,服务器收到请求后关闭,4次挥手成功,双方关闭连接