网络与多线程
TCP与UDP的区别
TCP
TCP三次对话的简单过程:
1)主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;
2)主机B向主机A发送同意连接和要求同步 (同步就是两台主机一个在发送,一个在接收,协调工作)的数据包 :“可以,你什么时候发?”,这是第二次对话;
3)主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”, 这是第三次对话。
注意:TCP建立连接要进行3次握手,而断开连接要进行4次
区别
1、基于连接与无连接;
2、对系统资源的要求(TCP较多,UDP少);
3、UDP程序结构较简单;
4、流模式与数据报模式 ;
5、TCP保证数据正确性,UDP可能丢包;
6、TCP保证数据顺序,UDP不保证。
了解Socket
socket定义
Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议
主要过程
服务端
from socket import *
ip_sort = ('127.0.0.1',8090)
back_log = 5
buffer_size = 1024
# 创建一个socket
tcp_server = socket(AF_INET,SOCK_STREAM)
# 绑定ip 和 端口号
tcp_server.bind(ip_sort)
# 设置back_log 半连接挂起数量
tcp_server.listen(back_log)
print('服务器启动了......')
# 设置监听,服务器阻塞
while True:
conn,addr = tcp_server.accept()
print('双向链接是',conn)
print('客户端地址',addr)
while True:
# 接受数据
try:
data = conn.recv(1024)
print('data from client',data.decode('utf-8'))
#发送数据
conn.send(data.upper())
except Exception:
break # 防止客户端突然断开链接导致 conn为空,继而产生异常
conn.close()
tcp_server.close()
客户端
from socket import *
ip_sort = ('127.0.0.1',8090)
back_log = 5
buffer_size = 1024
# 创建一个socket
tcp_client = socket(AF_INET,SOCK_STREAM)
# 连接服务器
tcp_client.connect(ip_sort)
while True:
msg = input('请输入:').strip()
if not msg: continue # 不允许什么也不发送
tcp_client.send(msg.encode('utf-8'))
data = tcp_client.recv(1024)
print('data from server-->',data.decode('utf-8'))
tcp_client.close()
使用socketserver实现并发
https://blog.csdn.net/weixin_42142216/article/details/91837216
多进程与多线程
多线程
1.创建线程的2种方式
- 直接使用Thread类创建
import threading
import time
def sayhi(num): # 定义每个线程要运行的函数
print("running on number:%s" % num)
time.sleep(3)
if __name__ == '__main__':
t1 = threading.Thread(target=sayhi, args=(1,)) # 生成一个线程实例
t2 = threading.Thread(target=sayhi, args=(2,)) # 生成另一个线程实例
t1.start() # 启动线程
t2.start() # 启动另一个线程
print(t1.getName()) # 获取线程名
print(t2.getName())
- 继承Thread类,重写run方法
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def 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.守护线程
无论是进程还是线程,主线程/进程结束之后守护线程一定会强制结束:
但是,进程和线程的结束时机是不一样的:
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细说明
1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
注意:主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
注意:守护线程在主线程运行结束后会立刻挂掉,但是如果守护线程先运行结束了,他也会挂掉
栗子1:
from threading import Thread
import threading
import time
def foo():
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("虽然主线程和foo都打印了,但主线程还活着,守护线程foo执行完毕已经死掉了,count", threading.active_count())
print("end456")
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("count", threading.active_count())
print("main-------")
执行结果:
123
456
count 3
main-------
end123
虽然主线程和foo都打印了,但主线程还活着,守护线程foo执行完毕已经死掉了,count 2
end456
分析:这里主线程和bar的线程还活着,主线程的代码虽然运行结束了,但是bar子线程还活着,主线程就死不掉,而foo虽然是守护线程,却已经先一步去世了
python中的GIL锁
每个进程内,同一时刻只能执行一个线程,所以在python中如果要利用多核的优势就要执行多个进程
GIL锁和Lock
GIL 与 Lock 是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显 GIL 不负责这件事,只能用户自定义加锁处理,即 Lock
同步锁
同步锁:控制同一资源同一时刻内只能一个线程访问,强调执行的先后顺序,join是也是为了同步,是让主线程等待join的子线程运行完毕再执行下面的代码,但是他锁住的是整个线程的代码,执行效率低。因此要使用同步锁。
import time
import threading
def addNum():
global num #在每个线程中都获取这个全局变量
# num-=1
lock.acquire()
temp=num
print('--get num:',num )
time.sleep(0.1)
num =temp-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)
死锁与递归锁
死锁
定义
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
四个必要条件
- 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
- 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
- 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
演示:
# 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,
# 因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
import threading,time
class myThread(threading.Thread):
def doA(self):
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
lockB.release()
lockA.release()
def doB(self):
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
lockA.release()
lockB.release()
def run(self):
self.doA()
self.doB()
if __name__=="__main__":
lockA=threading.Lock()
lockB=threading.Lock()
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()#等待线程结束,后面再讲。
## 当线程1运行到doB()的时候,此时A已经解锁,而线程2拿到了A锁,而线程1也拿到了B锁
# ,继续执行,线程1卡在了拿A锁,线程2卡在了拿B锁
# 解决办法:使用递归锁,将
#
# lockA=threading.Lock()
# lockB=threading.Lock()<br>#--------------<br>lock=threading.RLock()
分析:线程1拿到了A锁,线程2拿到了B锁,而释放A要用到B锁,而释放B要用到A锁,这就造成了死锁
死锁的解决,使用递归锁
递归锁
import time
import threading
class Account:
def __init__(self, _id, balance):
self.id = _id
self.balance = balance
self.lock = threading.RLock()
def withdraw(self, amount):
with self.lock:
self.balance -= amount
def deposit(self, amount):
with self.lock:
self.balance += amount
def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景
with self.lock:
interest=0.05
count=amount+amount*interest
self.withdraw(count)
def transfer(_from, to, amount):
#锁不可以加在这里 因为其他的其它线程执行的其它方法在不加锁的情况下数据同样是不安全的
_from.withdraw(amount)
to.deposit(amount)
alex = Account('alex',1000)
yuan = Account('yuan',1000)
t1=threading.Thread(target = transfer, args = (alex,yuan, 100))
t1.start()
t2=threading.Thread(target = transfer, args = (yuan,alex, 200))
t2.start()
t1.join()
t2.join()
print('>>>',alex.balance)
print('>>>',yuan.balance)
# 总结:Rlock 递归锁内部机制每次 acquire ,count加1,每次release,count减1,
# 当count>0时,其他所有线程都不能访问该资源
# 为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。
# RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数
# ,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
- 为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。
- RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数 ,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。
信号量
import threading,time
class myThread(threading.Thread):
def run(self):
if semaphore.acquire():
print(self.name)
time.sleep(5)
semaphore.release()
if __name__=="__main__":
semaphore=threading.Semaphore(5)
thrs=[]
for i in range(100):
thrs.append(myThread())
for t in thrs:
t.start()
# 信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,
# 每当调用acquire()时-1,调用release()时+1。
#
# 计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,
# 直到其他线程调用release()。(类似于停车位的概念)
#
# BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值
# 是否超过了计数器的初始值,如果超过了将抛出一个异常
Event事件-进行条件同步
import threading,time
class Boss(threading.Thread):
def run(self):
print("BOSS:今晚大家都要加班到22:00。")
event.isSet() or event.set() # set设置flag为True,使wait通行
time.sleep(5)
print("BOSS:<22:00>可以下班了。")
event.isSet() or event.set() # 这行代码确保event变成True
class Worker(threading.Thread):
def run(self):
event.wait()
print("Worker:哎……命苦啊!")
time.sleep(0.25)
event.clear()
event.wait()
print("Worker:OhYeah!")
if __name__=="__main__":
event=threading.Event()
threads=[]
for i in range(5):
threads.append(Worker())
threads.append(Boss())
for t in threads:
t.start()
for t in threads:
t.join()
打印结果:
BOSS:今晚大家都要加班到22:00。
Worker:哎……命苦啊!
Worker:哎……命苦啊!
Worker:哎……命苦啊!Worker:哎……命苦啊!
Worker:哎……命苦啊!
BOSS:<22:00>可以下班了。
Worker:OhYeah!
Worker:OhYeah!
Worker:OhYeah!
Worker:OhYeah!
Worker:OhYeah!
Condition复杂线程的逻辑同步
线程通信,队列
用法类似于进程,这里主要介绍常用的三种队列
- Queue先进先出
- LifoQueue先进后出
- PriorityQueue优先级队列,按进入时设置的优先级出,优先级高的线出队
线程池
多进程
1.创建进程的2种方式
- 通过Process直接创建
from multiprocessing import Process
import os
# os.getpid() 获取进程ID
# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
## 创建进程的方法一
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
# 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,
# 用start()方法启动,这样创建进程比fork()还要简单。
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
# join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
p.join()
print('Child process end.')
join 主进程等子进程结束
- 创建类继承Process,重写run方法
### 创建进程的第二种方式
from multiprocessing import Process
import time
import random
class Piao(Process):
def __init__(self,name):
# self.name=name
# super().__init__() #Process的__init__方法会执行self.name=Piao-1,
# #所以加到这里,会覆盖我们的self.name=name
super().__init__()
self.name = name
def run(self):
print('%s piao start'%self.name)
time.sleep(random.randrange(1,5))
print('%s piao end'%self.name)
if __name__ == "__main__":
p1 = Piao('egon')
p2 = Piao('alex')
p3 = Piao('Jark')
p1.start() # start会自动调用run
p2.start()
p3.start()
print('主线程')
僵尸进程与孤儿进程
- 僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
简单概括,死掉的进程没有被父进程用wait回收,他就是僵尸进程
僵尸进程解决办法:
解决方法一:杀死父进程
解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
- 孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
简单概括:父进程挂掉了,子进程还活着,他们就成了孤儿,需要被init(pid=1)收养
守护进程
牢记以下3点:
- 进程结束,守护进程立即结束,无论有没有执行完毕
- 进程的内的代码全部执行完毕就代表进程结束了,这一点与线程不同
- 进程没结束,守护进程可以先结束
栗子1:
#主进程代码运行完毕,守护进程就会结束
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(3)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__=='__main__':
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True
p1.start()
p2.start()
time.sleep(1)
print("main-------") #打印该行则主进程代码结束,则守护进程p1应该被终止,
# 可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止
#### 注意:和线程不一样的是,主进程的执行结束,就是主进程内的代码运行结束,此时它的守护进程就会强制结束
打印结果为:
456
123
main-------
end456
分析:p1是守护进程,所以主进程结束p1立即结束(还来不及打印end123),p2是普通子进程,main结束他就成了孤儿,被init收养了,但会继续执行直至结束
栗子2:
# 主进程创建守护进程
#
# 其一:守护进程会在主进程代码执行结束后就终止
#
# 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError:
# daemonic processes are not allowed to have children
#
# 注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process
import time
import random
class Piao(Process):
def __init__(self,name,num):
super().__init__()
self.name=name
self.num = num
def run(self):
print('%s is piaoing' %self.name)
i = 0
while i < self.num:
time.sleep(1)
i += 1
print('i:', i)
time.sleep(random.randrange(1,3))
print('%s is piao end' %self.name)
print('i:',i)
if __name__=='__main__':
p=Piao('egon',3)
p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
p.start()
time.sleep(8) # 主进程结束后,守护进程立即终止,不管有没有执行完毕
print('p守护进程还活着吗?',p.is_alive()) # 测试守护进程代码运行完之后,主进程还没运行完,看守护进程还活着吗
print('主')
### 如果主进程还没运行完毕,守护进程就运行完毕了,那守护进程就算挂掉了
打印结果为:
egon is piaoing
i: 1
i: 2
i: 3
egon is piao end
i: 3
p守护进程还活着吗? False
主
分析:在这个栗子中,守护进程比主进程先一步去世,可以看到他已经挂掉了。
进程同步锁
- lock
#由并发变成了串行,牺牲了运行效率,但避免了竞争
from multiprocessing import Process,Lock
import os,time
def work(lock):
lock.acquire()
print('%s is running' %os.getpid())
time.sleep(2)
print('%s is done' %os.getpid())
lock.release()
if __name__ == '__main__':
lock=Lock()
for i in range(3):
p=Process(target=work,args=(lock,))
p.start()
# 加锁:由并发变成了串行,牺牲了运行效率,但避免了竞争
加锁:由并发变成了串行,牺牲了运行效率,但避免了竞争
进程通信,队列和管道
进程之间可以使用队列和管道进行通信,这里墙裂建议使用队列
- 队列通信
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
- 管道通信
from multiprocessing import Process,Pipe
import time,os
def consumer(p,name):
left,right=p
left.close()
while True:
try:
baozi=right.recv()
print('%s 收到包子:%s' %(name,baozi))
except EOFError:
right.close()
break
def producer(seq,p):
left,right=p
right.close()
for i in seq:
left.send(i)
# time.sleep(1)
else:
left.close()
if __name__ == '__main__':
left,right=Pipe()
c1=Process(target=consumer,args=((left,right),'c1'))
c1.start()
seq=(i for i in range(10))
producer(seq,(left,right))
right.close()
left.close()
c1.join()
print('主进程')
# 基于管道实现进程间通信(与队列的方式是类似的,队列就是管道加锁实现的)
'''
注意:生产者和消费者都没有使用管道的某个端点,就应该将其关闭,
如在生产者中关闭管道的右端,在消费者中关闭管道的左端。如果忘记执行这些步骤,
程序可能再消费者中的recv()操作上挂起。管道是由操作系统进行引用计数的,
必须在所有进程中关闭管道后才能生产EOFError异常。
因此在生产者中关闭管道不会有任何效果,付费消费者中也关闭了相同的管道端点。
'''
使用JoinableQueue完成生产者,消费者模型
JoinableQueue介绍:
JoinableQueue同样通过multiprocessing使用。
创建队列的另外一个类:
JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
- q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
- q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
from multiprocessing import Process, JoinableQueue
import time, random, os
def consumer(q):
while True:
print('q.size',q.qsize())
res = q.get()
time.sleep(random.randint(1, 3))
print('\033[45m%s 吃 %s\033[0m' % (os.getpid(), res))
q.task_done() # 向q.join()发送一次信号,证明一个数据已经被取走了
def producer(name, q):
for i in range(10):
time.sleep(random.randint(1, 3))
res = '%s%s' % (name, i)
q.put(res)
print('\033[44m%s 生产了 %s\033[0m' % (os.getpid(), res))
q.join() #生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。
# 阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
if __name__ == '__main__':
q = JoinableQueue()
# 生产者们:即厨师们
p1 = Process(target=producer, args=('包子', q))
p2 = Process(target=producer, args=('骨头', q))
p3 = Process(target=producer, args=('泔水', q))
# 消费者们:即吃货们
c1 = Process(target=consumer, args=(q,))
c2 = Process(target=consumer, args=(q,))
c1.daemon = True
c2.daemon = True
# 开始
p_l = [p1, p2, p3, c1, c2]
for p in p_l:
p.start()
p1.join()
p2.join()
p3.join()
print('主')
# 主进程等--->p1,p2,p3等---->c1,c2
# p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
# 因而c1,c2也没有存在的价值了,应该随着主进程的结束而结束,所以设置成守护进程
信号量
# 互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,
# 比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去,
# 如果指定信号量为3,那么来一个人获得一把锁,计数加1,当计数等于3时,后面的人均需要等待。
# 一旦释放,就有人可以获得一把锁
# 信号量与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念
from multiprocessing import Process,Semaphore
import time,random
def go_wc(sem,user):
sem.acquire()
print('%s 占到一个茅坑' %user)
time.sleep(random.randint(0,3)) #模拟每个人拉屎速度不一样,0代表有的人蹲下就起来了
sem.release()
if __name__ == '__main__':
sem=Semaphore(5)
p_l=[]
for i in range(13):
p=Process(target=go_wc,args=(sem,'user%s' %i,))
p.start()
p_l.append(p)
for i in p_l:
i.join()
print('============》')
# 信号量Semahpore(同线程一样)
事件(同线程一样)
# python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法
# set、wait、clear。
#
# 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为
# False,那么当程序执行
# event.wait
# 方法时就会阻塞,如果“Flag”值为True,那么event.wait
# 方法时便不再阻塞。
#
# clear:将“Flag”设置为False
# set:将“Flag”设置为True
# _*_coding:utf-8_*_
# !/usr/bin/env python
from multiprocessing import Process, Event
import time, random
def car(e, n):
while True:
if not e.is_set(): # Flase
print('\033[31m红灯亮\033[0m,car%s等着' % n)
e.wait()
print('\033[32m车%s 看见绿灯亮了\033[0m' % n)
time.sleep(random.randint(3, 6))
if not e.is_set():
continue
print('走你,car', n)
break
def police_car(e, n):
while True:
if not e.is_set():
print('\033[31m红灯亮\033[0m,car%s等着' % n)
e.wait(1)
print('灯的是%s,警车走了,car %s' % (e.is_set(), n))
break
def traffic_lights(e, inverval):
while True:
time.sleep(inverval)
if e.is_set():
e.clear() # e.is_set() ---->False
else:
e.set()
if __name__ == '__main__':
e = Event()
# for i in range(10):
# p=Process(target=car,args=(e,i,))
# p.start()
for i in range(5):
p = Process(target=police_car, args=(e, i,))
p.start()
t = Process(target=traffic_lights, args=(e, 10))
t.start()
print('============》')
# Event(同线程一样)
进程池
- 同步调用进程池
from multiprocessing import Pool
import os,time
def work(n):
print('%s run' %os.getpid())
time.sleep(3)
return n**2
if __name__ == '__main__':
p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
res_l=[]
for i in range(10):
res=p.apply(work,args=(i,)) #同步调用,直到本次任务执行完毕拿到res,等待任务work执行的过程中可能有阻塞也可能没有阻塞,但不管该任务是否存在阻塞,同步调用都会在原地等着,只是等的过程中若是任务发生了阻塞就会被夺走cpu的执行权限
res_l.append(res)
print(res_l)
# 同步调用apply
- 异步调用进程池
from multiprocessing import Pool
import os,time
def work(n):
print('%s run' %os.getpid())
time.sleep(3)
return n**2
if __name__ == '__main__':
p=Pool(3) #进程池中从无到有创建三个进程,以后一直是这三个进程在执行任务
res_l=[]
for i in range(10):
res=p.apply_async(work,args=(i,)) # 这里不会阻塞,会执行运行下面的代码,所分配的任务会异步执行
res_l.append(res)
#异步apply_async用法:如果使用异步提交的任务,主进程需要使用join,等待进程池内任务都处理完,然后可以用get收集结果,否则,主进程结束,进程池可能还没来得及执行,也就跟着一起结束了
p.close()
p.join()
for res in res_l:
print(res.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
# 异步调用apply_async
进程与线程的区别
- 一个进程由多个线程组成
- 每个进程之间的内存相互独立,而线程共享同一块内存
- 进程不受GUI锁的限制,可以并发,线程不可以
- 进程的开销大于线程
- 执行结束的时机不一样,主进程的执行结束,就是主进程内的代码执行结束,而主线程是所有非守护线程运行结束
- 主线程的结束意味着进程的结束,进程整体的资源都将被回收,而线程必须保证非守护线程都运行完毕后才能结束。
同步锁和互斥锁的区别
两者都包括对资源的独占。
区别是
1:互斥是通过竞争对资源的独占使用,彼此没有什么关系,也没有固定的执行顺序。
2:同步是线程通过一定的逻辑顺序占有资源,有一定的合作关系去完成任务。
协程
定义
协程的本质就是在单线程下,由用户自己控制一个任务遇到 io 阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
#1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
#2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
一句话说明:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
优点:
#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
#2. 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点:
#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
使用gevent实现协程
import gevent
from gevent import monkey;monkey.patch_all()
import time
def func1(name):
print(f'{name} func1 start......')
time.sleep(2)
print(f'{name} func1 end......')
def func2(name):
print(f'{name} func2 start......')
time.sleep(3)
print(f'{name} func2 end......')
g1 = gevent.spawn(func1,'小明')
g2 = gevent.spawn(func2,'Mitchell')
g1.join()
g2.join()
# gevent.joinall([g1,g2])
print('Main Thread end...')
IO模型
- 阻塞IO Blocking IO
blocking IO 的特点就是在 IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被 block 了。
- 非阻塞IO No-Blocking-IO
在非阻塞式 IO 中,用户进程其实是需要不断的主动询问 kernel 数据准备好了没有。
- IO 多路复用(IO multiplexing)
它的基本原理就是 select/epoll 这个 function 会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程
结论: select 的优势在于可以处理多个连接,不适用于单个连接
- 异步IO
用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了
- 各种IO比较
select
import socket
import select
sk=socket.socket()
sk.bind(("127.0.0.1",9904))
sk.listen(5)
while True:
r,w,e=select.select([sk,sk],[],[],5)
r=[sk,]
for i in r:
conn,add=i.accept()
print(conn)
print("hello")
print('>>>>>>')
selector
import selectors
import socket
import os
import json
sel = selectors.DefaultSelector()
def accept(sock,mask):
conn,addr = sock.accept()
print('accept',conn)
conn.setblocking(False) # 设置IO不阻塞
sel.register(conn,selectors.EVENT_READ,read)
def read(conn,mask):
try:
data = conn.recv(1024)
dic = json.loads(data.decode('utf-8'))
print(repr(dic))
filename = dic['filename']
filesize = int(dic['filesize'])
if not os.path.exists(filename):
f = open(filename,'ab')
length = 0
while length < filesize:
msg = conn.recv(1024)
f.write(msg)
length += len(msg)
# if not data:
# raise Exception()
# print('echoing', repr(data), 'to', conn)
# conn.send(data.upper()) # Hope it won't block
except Exception as e:
print(e)
sel.unregister(conn)
conn.close()
sock = socket.socket()
sock.bind(('localhost',8090))
sock.listen(5)
sock.setblocking(False)
sel.register(sock,selectors.EVENT_READ,accept)
print('server......')
while True:
event = sel.select()
for key,mask in event:
func = key.data
print('mask',mask)
func(key.fileobj,mask)
参考资料
https://www.cnblogs.com/linhaifeng/articles/6817679.html#_label2