23 并发编程 03
并发编程
昨日内容回顾
进程对象属性及其他方法
from multiprocessing import Process,current_process
import os
current_process().pid #查看当前进程号
os.getpid() #查看当前进程号
os.getppid()#查看当前进程的父进程号
windows中终端命令
tasklist
tasklist|findstr pid
僵死进程与孤儿进程
僵死进程
孤儿进程
守护进程
被守护进程结束后守护进程立刻也跟着结束
如何开启:在start之前写p.daemon=True
p.start()
互斥锁
"""
多个人在操作同一份数据的时候,可能会出现数据错乱的问题
针对上述问题我们正常都是加锁处理
作用:将并发变成串行,牺牲了程序运行的效率但是保证了数据的安全
注意:只在操作数据的部分加锁即可
锁尽量不要自己去处理,很容易造成死锁现象
"""
扩展:行锁,表锁
#抢锁
q.acquire()
#释放锁
q.release()
#模拟抢锁
队列Queue
队列:先进先出
堆栈:先进后出
from multiprocessing import Queue
q=Queue()
q.put()
q.get()
q.full()
q.empty()
q.get_nowait()#取数据的时候如果没有数据直接报错
q.get(timeout=5)
进程间通信
进程间无法直接进行数据交互,但是可以通过队列或者管道实现数据交互
本地测试的时候可能会用到Queue,实际生产用的都是别人封装好的功能非常强大的工具
生产者与消费者模型
生产者+消息队列+消费者
为何要有消息队列,是为了解决供需不平衡的问题
#joinableQueue
可以被等待的q
你在往队列中放数据的时候,内部会有一个计数器自动加1
你在往队列中放数据的时候,调用task_done(),内部计数器自动减1
q.join() 当计数器为0的时候才继续往下运行
线程理论
进程:资源单位
线程:执行单位。线程是真正干活的人,干活的过程中所需的资源由线程所在的进程提供
每一个进程肯定都自带一个线程
同一个进程内可以创建多个线程
开进程:申请内存空间,拷贝代码,消耗资源较大
开线程:同一个进程内创建多个线程,无需上述2个步骤,消耗资源相对较小
今日内容
开启线程的2种方式
#开启线程不需要在main下面执行代码,直接书写就可以
#但是我们习惯性的将启动写在main下
#针对两个双下划线开头双下划线开头的方法,读成双下init
from multiprocessing import Process
from threading import Thread
def task():
print("子线程")
if __name__ == '__main__':
t = Process(target=task)
t.start()
print("主线程")
# from multiprocessing import Process
# from threading import Thread
# def task():
# print("子线程")
# if __name__ == '__main__':
# t=Thread(target=task)
# t.start()
# print("主线程")
如何实现tcp服务端并发的效果
import socket
from threading import Thrread
from multiprocess import Process
#服务端要有固定的端口 24小时不间断提供服务 能够支持并发
server=socket.socket() #括号内不加参数默认是tcp协议
server.bind(("127.0.0.1",8080))
server.listen(5)
#链接循环
def talk(conn):
while True:
try:
data=conn.recv(1024)
if len(data)==0:break
print(data.decode("utf-8"))
conn.send(data.upper())
except ConnectionResetError as e
print(e)
break
conn,close()
while True:
conn,addr=server.accept()
t=Thread(target=talk,args=(conn,))
t.start()
#客户端
import socket
client=socket.socket()
client.connect(("127.0.0.1",8080))
while True:
client.send(b"hello")
data=client.recv(1024)
print(data.decode("utf-8"))
线程对象的join方法
t.join 同进程
同一个进程下的多个线程数据共享
from threading import Thread
import time
money=100
def task():
global money
money=666
if __name__ == '__main__':
t=Thread(target=task)
t.start()
print(money)
线程对象及其他方法
rom threading import Thread,active_count,current_thread
import time,os
def task():
#print("hello",os.getpid())
print("hello",current_thread().name)
if __name__ == '__main__':
t=Thread(target=task)
t.start()
print("主",active_count())
#print("主",os.getpid())
print("主",current_thread().name)
守护线程
主线程运行结束之后不会立即结束,会等待其他非守护线程结束才结束
因为主线程的结束意味着所在进程的结束
t.setDaemon()
线程互斥锁
from threading import Thread,Lock
import time
money=100
lock=Lock()
def task():
global money
lock.acquire()
tmp=money
time.sleep(0.1)
money=tmp-1
lock.release()
if __name__ == '__main__':
t_list=[]
for i in range(100):
t=Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
GIL全局解释器锁
python解释器有多个版本,cpython,pypypython,但普遍使用的是cpython
在cpython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行
同一进程下的多个线程无法利用多核优势
因为cpython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
1.引用计数
2.标记清楚
3.分代回收
重点:1.GIL不是python的特点,而是cpython解释器的特点
2.GIL是保证解释器级别的数据安全
3.同一进程下的多个线程无法同时进行 ,即无法利用多核优势
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病同意进程下多个线程无法利用多核优势
GIL与普通锁的区别
同一个进程下的多线程无法利用多核优势,是不是就没有用了
多线程分单核多核
计算密集型:单核:多进程:额外的消耗资源
多线程:
多核:多进程:总耗时10+
多线程:总耗时40+
I/O密集型:多核:多进程:相对资源浪费
多线程:更加节省资源
### 死锁与递归锁
死锁案列
import time
from threading import Thread,Lock,RLock
lock1 = Lock()
lock2 = Lock()
# lock1 = RLock() # 递归锁, 可重入锁
# lock2 = RLock()
def eat1( name):
lock1.acquire()
print("%s抢到了面条" % name)
time.sleep(1)
lock2.acquire()
print("%s抢到了筷子" % name)
time.sleep(1)
lock2.release()
print("%s放下了筷子" % name)
time.sleep(1)
print("%s放下了面条" % name)
lock1.release()
def eat2(name):
lock2.acquire()
print("%s抢到了筷子" % name)
time.sleep(1)
lock1.acquire()
print("%s抢到了面条" % name)
time.sleep(1)
lock1.release()
print("%s放下了面条" % name)
time.sleep(1)
print("%s放下了筷子" % name)
lock2.release()
if __name__ == '__main__':
for name in ['egon', 'jason', 'ly']:
t = Thread(target=eat1, args=( name,))
t.start()
for name in ['qq', 'tom', 'kevin']:
t1 = Thread(target=eat2, args=(name,))
t1.start()
递归锁案列
递归锁的特点:
可以被连续的acquire和release
但是只能被第一个抢到这把锁的执行上述操作
他的内部有一个计数器,每acquire一次计数加一次 每release一次计数减一次,只要计数不为0,那么其他人就无法抢到该锁
信号量
信号量在不同的阶段可能对应不同的技术点
在并发编程中信号量指的是锁
如果我们将互斥锁比喻程一个厕所的话,那么信号量就相当于多个厕所
sm=semaphore(5)#括号内写数字,写几就表示开设几个坑位
from threading import Thread, Semaphore
from multiprocessing import Process, Lock
# Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据
import time, random
sm = Semaphore(2)
def task(i):
sm.acquire()
print("线程:%s,进来了" % i)
time.sleep(random.randint(1, 3))
print("线程:%s,出去了" % i)
sm.release()
if __name__ == '__main__':
for i in range(6):
t = Thread(target=task, args=(i,))
t.start()
Event事件
一些线程或者进程需要等待另外一些进程/线程运行完毕之后才能运行,类似于发射信号一样
from threading import Thread,Event
import time
event=Event() #造了一个红绿灯
def light():
print("红灯亮着的")
time.sleep(3)
print("绿灯亮了")
event.set()
def car(name):
print("%s 车正在等红灯" %name)
event.wait()
print("%s 车加油门开走了" %name)
if __name__=="__main__":
t=Thread(target=light)
t.start()
for i in range(10):
t=Thread(target=car,args=("%s" %i))
t.start()