同步阻塞IO
同步阻塞进行 Receive 时, 用户线程发送 read 请求到达操作系统内核, 等待数据到达, 数据到达后会先将数据拷贝到用户线程的缓冲区, read 完成.
同步非阻塞IO
同步非阻塞 socket 实现并发
外循环用来不断的 accept(接收连接), 用一个列表来保存所有建立的对等 socket , 并设置为非阻塞. 内循环用来循环这个保存对等 socket 是否有数据传入.
importsocket
server=socket.socket()
server.setblocking(False)#设置非阻塞.server.bind(('', 6969))
server.listen(5)
client_list= [] #存放连接进来的客户端.#设置一个大循环,接收用户请求.
whileTrue:#接收请求.
try:
conn, addr=server.accept()
conn.setblocking(False)#设置这个对等连接为非阻塞.
client_list.append((conn, addr))print('连接: {}'.format(addr))exceptBlockingIOError:pass
#处理请求.
for client, addr inclient_list:try:
recv_data= client.recv(1024) #收数据.
ifrecv_data:print('接收来自:{}>>>'.format(addr, recv_data.decode('utf-8')))
client.send(recv_data)#发送数据.
else:#关闭连接.
client_list.remove((client, addr)) #从列表中删除
client.close()print('断开连接: {}'.format(addr))exceptBlockingIOError:pass
IO多路复用
epoll (事件驱动IO) 为 Linux 下所有.
selector (非阻塞socket实现并发服务器) 是 windows 下的,通过循环的方式,当连接越多,处理就会越慢.
import socket
importselectors
server=socket.socket()
server.bind(('', 6969))
server.listen(1000)
def recv(soc):
data = soc.recv(1024)
if data:
soc.send(data)
else:
# 删除注册.
epoll_selector.unregister(soc)
soc.close()
def accept(soc):
# 接收请求.
conn, addr = soc.accept()
# 把对等连接注册到epoll监视器.
epoll_register(
conn,
selectors.EVENT_READ,
recv
)#创建一个epoll监视器.
① epoll_selector = selectors.DefaultSelector() # 根据操作系统自己选择.
② epoll_selector = selectors.EpollSelector() # Linux下才可以, windows 下会报错.
# 注册server.
epoll_selector.register(server, # 主服务的socket.
selectors.EVENT_READ, # 事件类型.
accept # 回调函数.
)
# 循环监听.
while True:
events = epoll_selector.select()
# events 就是所有被触发了的事件.
for key, mark in events:
sock = key.fileobj # socket
callback = key.data
# 执行回调函数.
callback(sock)
print(events)
exit()
非阻塞套接字实现多人聊天室
importsocket
server=socket.socket()
server.setblocking(False)
server.bind(("", 6969))
server.listen(1000)
clients=[]whileTrue:#循环接收客户端
try:
conn, addr=server.accept()
conn.setblocking(False)
clients.append((conn, addr))exceptException:pass
#循环处理对等连接.
for client, addr inclients:# 收数据.
try:
recv_data = client.recv(1024)
if recv_data:
# 给所有人发一份.
data = 'receive {} from {}'.format(
recv_data.decode('utf-8'),
addr
)
for c, a in clients:
c.send(data.encode())
else:
clients.remove((client, addr))
client.close()
except Exception:
pass
并发
在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上面运行, 但任一时刻点上只有一个程序在处理机上运行.
并行
当系统有一个以上 cpu 时,则程序的操作有可能非并发. 当一个 cpu 执行一个程序时,另一个 cpu 可以执行另一个程序,两个程序互不抢占 cpu 资源,可以同时进行.
Knowledge expansion--01
计算机程序只是存储在磁盘上的可执行二进制(或其它类型) 文件,只有把他们加载在内存中并被操作系统调用,才拥有其生命周期。
操作系统调度算法: 时间片轮转、优先级调度.
进程
#1. 进程则是一个执行中的程序.
#2. 每个进程都拥有自己的地址空间,内存,数据栈以及其它用于跟踪执行的辅助数据.
#3.操作系统管理其上所有进程的执行,并为这些进程合理分配时间.
importtimeimportrandomdeffun():
time.sleep(random.randint(1,3))if __name__ == "__main__":
s_time=time.time()
fun()
fun()
e_time=time.time()print('耗时 %s 秒' % (e_time - s_time))
多进程提高运行效率
importtimefrom multiprocessing importProcessdeffun(arg)print(1)
time.sleep(3)if __name__ == '__main__':
s_time=time.time()
p1= Process(target=fun, (arg)) #创建一个子进程, fun 是要执行的函数.
#启动这个进程.
p1.start()
fun()
e_time=time.time()print(e_time - s_time)
from multiprocessing importProcessimporttimedeffun1(a, b):
time.sleep(2)
res= a +bprint(res)if __name__ == '__main__':
s_time=time.time()
fun1(1, 1)
p1= Process(target=fun1, args = (2, 2))
p1.start()
e_time=time.time()print(e_time - s_time)
线程
#1. 有时被称为轻量进程 (Lightweight Process, LWP), 是程序执行流的最小单元.
#2. 多任务可以由多进程完成, 也可以由一个进程内的多线程完成. 一个进程至少有一个线程.
#3. 让步: 当其它线程运行时, 它可以被抢占(中断)和临时挂起(睡眠).
#4. 线程的轮询调度机制类似于进程的轮询调度. 只不过这个调度不是有操作系统来负责, 而是由 Python 解释器来负责.
importtimefrom threading importThreaddeffun():print('任务开始了')
time.sleep(2)if__name__ == '__main__':
s_time=time.time()
t1= Thread(target=func)
t1.start()
fun()
e_time=time.time() print(e_time - s_time)
Knowledge expansion--02
多进程并行的必要条件
总进程数量不多于 CPU 核心数量, 因此现在运行的程序都是轮询调度产生的并行假象. 但是在 Python 程序, 用户层面的确获得了并行.
在 Python 里面, 多线程是不能并行的.
IO 密集型任务, Python 的多线程起到作用. CPU 密集型任务, 多线程占不到优势, 还有可能因为争夺资源而变慢.
GIL(Global Interpreter Lock) 全局解释性锁
Python 设计之初, 还没有多核处理器的概念. 为了设计方便和线程安全, 直接设置了一个锁. 这个锁要求, 任何进程中一次只能由一个线程在执行.
因此并不能为多个线程分配多个 cpu. 所以 Python 中的线程只能实现并发, 而不能实现真正的并行.
Python3 中 GIL 锁有一个很棒的设计, 在遇到任何 IO 阻塞(不是耗时)的时候, 会自动i切换线程.
多进程实现并发服务器
importmultiprocessingimportsocket
import os.getpid
server=socket.socket()
server.bind(('', 6969))
server.listen(1000)
def task(sock) # 子进程中的任务逻辑.
print('我是进程 %s ' % os.getpid())
while True:
recv_data = sock.recv(1024)
if recv_data:
print(recv_data.decode('utf-8'))
sock.send(recv_data)
else:
break
sock.close()whileTrue:
conn, addr=server.accept()#每当有一个进程连接进来时, 我们就当作一个新的进程去处理.
process =multiprocessing.Process(
target = task
args = (conn)
)
process.start()
进程标识符
#1. 父进程
#2. 进程 ID、PID
#3. os.getpid() # 当前进程ID.
#4. os.getppid() # 父进程的ID.
from multiprocessing importProcessimporttime
import osdefchild_process_task(name):
time.sleep(1)print('我是子进程 %s,我的父进程id 是 %s' %(
name, os.getpid(), os.getppid()
))
if __name__ == '__main__':
print('我是父进程 id 是 %s' % os.getpid())
c_p = Process(target=child_process_task, args=('test'), name='test')
print('子进程 %s id 是 %s 准备启动了' % (c_p.name, c_p.pid))
c_p.start()
print('父进程结束')
线程标识符
#1. 主线程
任何一个进程, 默认会启动一个线程. 这个线程就是主线程,.
#2. 线程 ID
threading 有一个 current_thread(), 可以返回当前线程的实例. 还有一个 main_thread() 返回当前进程的主线程实例. ident 属性获取线程实例的 id.
#3. 解释器指定一个非零的整数.
import threading
import time
def child_thread_task():
print('我是线程 %s 我的id是 %s 主线程 id 是 %s' % (
threading.current_thread().name,
threading.current_thread().ident,
threading.main_thread().name
))
if __name__ == '__main__':
print('我是主线程, 我的 id 是 %s' % threading.current_thread.ident)
c_t = threading.Thread(target=child_thread_task, name='test')
# c_t.daemon = True # 设置为守护模式.
print('子线程 %s id 是 %s 准备启动' % (c_t.name, c_t.ident))
c_t.start()
print('主线程结束')
多线程与多进程的同步
守护模式
#1. daemon 默认是 False, 设置为 True, 守护模式.
#2. 运行完毕并非是终止运行, 对于父进程来说, 指的是父进程代码运行完毕.
importmultiprocessingimporttimedeffun(n, name):
time.sleep(n)print('我是 %s' %name)if __name__ == '__main__':
p1= multiprocessing.Process(target=fun, args=(3, '守护进程'))
p1.daemon = True
p2= multiprocessing.Process(target=fun, args=(10, '子进程 10秒'))
p3= multiprocessing.Process(target=fun, args=(15, '子进程 15秒'))
p1.start()
p2.start()
p3.start()
time.sleep(1)
协程
进程和线程面临着用户态和内核态的切换问题(耗费许多切换时间), 协程是进程和线程的升级版(用户自己控制切换时机, 不需要陷入系统的内核态.)