网络通讯 编码 socket socketserver 模块 线程 锁 同步条件 多线程利器 进程
本节内容
1、网络通讯三要素
2、编码 数据类型
3、socket模块
4、socketserver 模块
5、线程
6、同步锁、死锁、递归锁、信号量、条件变量
7、同步条件(事件)Event
8、多线程利器queue(队列)
9、进程
一、网络通讯三要素
网络通讯步骤;确定对方IP地址→确定应用程序端口 →确定通讯协议
三要素:a、IP地址 (1)用来标示网络上一台独立的主机。
(2)ip地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段。主机号:用于识别改网络的主机)
(3)特殊的ip地址:127.0.0.1(本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障。表示本机。
b、端口号 (1)用于标识进程的逻辑地址。不同的进程都有不同的端口标示。
(2)端口要将数据发送到指定的应用程序上,为了表示这些应用程序,所以给这些网络应用程序都用数字进行标识。为了方便称呼这些数字,则将这些数字称为端口。(此端口是一个逻辑端口)
c、传输协议;通讯的规则。列如TCP 、UDP协议(好比两个人得用同一种语言交流)
总结;网络通讯的过程其实就是一个(源端)不断封装数据包和(目的端)不断拆数据包的过程。
二、编码 数据类型:
什么是编码。基本概念,首先,我们从一段信息即消息说起,消息以人类理解、易懂的表示存在。我们打算将这种表式为:“明文”。对于说英语的人,纸张上打印的或屏幕上显示的英文单词都算作明文。
其次,我们需要能将明文表示的消息转成另外某种表示,我们还需要能将编码文本转回成明文。从明文到编码文本的转换称为“编码”,从编码文本又转回成明文则为“解码”
Python 2 里面最大的特点,就是混淆里Unicode bytes
Unicode对应的就明文
Python 3最重要的新特性大概要算是对文本和二进制数据,做了更为清晰的区分。文本总是Unicode,由str类型表示,二进制数据则有bytes类型表示。Python 3不会以任何隐式的方式混用str 和bytes,正是这两者的区分特别清晰。你不能拼接字符串和字节包,也无法在字节包里搜索字符串(反而亦然),也不能将字符串传入参数为字节包的函数(反之亦然)。这是好事
Python 3 对Unicode支持的最大变化是将会没有对bytes字节串的字段自动解码。如果你想要一个bytes字节串和一个Unicode相连接的话,你将会得到一个错误,不管你内容是什么。所有的这些在Python 2 中都将会有隐式处理,而在Python3 中你将会得到一个错误。
在Python 3 里只有两种数据类型 : str(Unicode) bytes 两种类型。
str存的Unicode就是和用户打交道的("hello") 这种形式
bytes,存的数据类型就是和计算机打交道的(b"hello") 这种形式
str ;什么内容可以称为是str 类型 Unicode 编码的 万国码 全世界都能看的懂得编码,存的就是一个字符
解码出来的就是 Unicode,是字符码 ("\u738b\u5bbd")用print打印出来就是明文了
import json
k = '王宽'
print(json.dumps(k))
print("\u738b\u5bbd")
a = 'hello 王宽' print(type(a)) #有字母 有汉字 存内存里都是 Unicode 编码 数据类型是str 存的是Unicode编码 #你看不见内部做的处理
str转换成bytes类型 叫 编码
为什么要转换成bytes类型,因为bytes类型是十六进制 ,里二进制就差一步,计算机好识别
怎么转换:
a = 'hello 王宽'
b = (bytes(a,'utf8'))
#为什么要用utf8 因为全球公用
print(b)
# 结果: b'hello \xe7\x8e\x8b\xe5\xae\xbd\
# \xe7\x8e\x8b 三个字节 \xe5\xae\xbd\三个字节 规定一个汉字三个字节
#hello 不变 因为阿斯克码表的hello和utf8的是一致的 所以英文不变,已经转换完了
print(type(b))
# <class 'bytes'>
b2 = a.encode('utf8') #encode 字符串方法 编码
print(b2)
# b'hello \xe7\x8e\x8b\xe5\xae\xbd' 和上面一样
bytes转换str 叫解码
bytes;
方法1
a = 'hello 王宽'
b = (bytes(a,'utf8'))
b2 = a.encode('utf8')
print(b2) #编码
# a = str(b2,'gbk')
#结果报错 因为 b2用utf8 编码不能用gbk解码
b3 = str(b2,'utf8')
print(b3) #解码
方法2 decode
a = 'hello 王宽'
b = (bytes(a,'utf8'))
b2 = a.encode('utf8')
print(b2) #编码
# a = str(b2,'gbk')
#结果报错 因为 b2用utf8 编码不能用gbk解码
# b3 = str(b2,'utf8')
b3 = b2.decode('utf8')
print(b3) #解码
3、socket
socket 是在应用层和传输层(TCP/IP协议族通讯)之间的一个抽象层,是一组接口,它把TCP/IP层复杂的操作抽象为几个简单接口供应用层调用以实现进程在网络中的通讯。
应用层通过“套接字”向网络发出请求或者答应网络请求。可以把socket理解为通讯的把手(先理解为以前的电话线)
socket 就是一个接口 传送的对象
socket 里有两个重要的参数 第一个family 第二个 type
family 就是地址族
family = AF_LNET :服务器之间的通信 默认参数进行网络通讯
family = AF_LNET6:服务器之间的通信
family = UNIX : Unix 不同进程间通信
type 两个参数 端口协议
SOCK_STREAM::TCP 连接模式、 默认参数
SOCK_Dgram::UDP
socket 编程 四字口诀 一收一发
服务端下的方法:
bind() 将套接字绑定到地址
listen() 开始接听 传入连接
accept() 接受连接并返回(conn address)
recv() 接受
send() 发送
sendall() 使劲发送 传送的内容一定是bytes类型
客户端下的方法:
connect() 连接到address处的套接字,一般addressd的格局为元组
recv() 接受
send() 发送
sendall() 使劲发送,传送的内容一定是bytes类型
close() 关
实现简单 的 通讯 能和一个让你人聊天 第二个聊的时候等着 第一个结束以后才能聊 可以实现三个排队
服务器端
sk = socket.socket() #建立连接tcp #里有两个默认参数
print(sk)
address = ('127.0.0.1',8000) #代指本机的ip地址 和端口号 测试用
sk.bind(address) #绑定方法 保定了ip地址和端口 #里面必须是元组
sk.listen(3) #决定 服务端可以容纳几个排队的人数
print('hello world')
while True: #实现了 客户端关闭 服务端 还可以接受下一个
conn,addr = sk.accept() #阻塞 等待客户端连接 打印一下
print(addr) #里面有两个元素 分别用两个变量去接受
#accept是一个元组类型 元组里面有两个对象一个是socket对象 一个是地址 分别用两个变量去接受
#conn是客户端的socket对象 服务端接受用 这个
#conn才是真正建立通道 sk是自己服务端创建端口 等待连接
#为什么要这样 应为 客户端可能有多个 在有其他人连接它的时候 也是进行一个绑定 用他自己的conn
#两个conn不一样 是他们各自的
#所以服务端都是通过conn进行 发送 接受数据
#为了验证serve 可以先发送
# inp = input('>>>')
# #3 里面必须是bytes 类型
# conn.send(bytes(inp,'utf8')) #send 发送意思
while True:
try: #异常处理 客户端突然中止 服务端还能正常执行
data = conn.recv(1024) #服务端用客户端的conn来接受
except Exception:
break
if not data:break #接受数据不为空的时候
print(str(data,'utf8'))
inp = input('>>>')
conn.send(bytes(inp,'utf8'))
conn.close
print(data)
客户端
import socket #客户端只需要做一件事情 连接
sk = socket.socket() #建立连接 和serve sk 没有任何关系
address = ('127.0.0.1',8000)
sk.connect(address)
#data = sk.recv(1024) #接受多少字节 有 阻塞
#data = sk.send(bytes('nb','utf8'))
# print(str(data,'utf8'))
while True:
inp = input('>>>')
if inp == 'exit': #如果输入exit 退出循环
break
sk.send(bytes(inp,'utf8'))
data = sk.recv(1024)
print(str(data,'utf8'))
sk.close()
执行一个 客户输入命令 服务端 执行 并返回在客户屏幕上
cmd服务端
import subprocess
import socket
sk = socket.socket()
print(sk)
address = ('127.0.0.1',8000)
sk.bind(address)
sk.listen(3)
print('hello world')
while True:
conn,addr = sk.accept()
print(addr)
while True:
try:
data = conn.recv(1024) #客户 发送过来的命令 在这接受
except Exception:
break
if not data:break
print(str(data,'utf8'))
obj = subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
cmd_result = obj.stdout.read()
#调用方法执行 接收过来客户的命令 windows没有 shell=True 不行
#stdout=subprocess.PIPE
#stdout 标准输出的意思
#subprocess.PIPE 管道的意思 由你的子进程 转到 主进程 转到能用的进程上来
#obj 把子进程内容传到obj
result_len = bytes(str(len(cmd_result)),'utf8') #转换类型
conn.sendall(result_len) #先发送多少字节
#有时候有粘包现象 就类似于一个船 准备走啊 东西少 看看后面还有没有了 时间很短
conn.recv(1024)#做一个隔断 客户端那边发送一下随变什么 ‘111’ 这样就隔开了因为两个sendall同时出现,有可能会粘包
conn.sendall(cmd_result) # bytes类型 发送
sk.close
cmd客户端
import socket
sk = socket.socket()
address = ('127.0.0.1',8000)
sk.connect(address)
while True:
inp = input('>>>')
if inp == 'exit':
break
sk.send(bytes(inp,'utf8'))
result_len = int(str(sk.recv(1024),'utf8')) #先接收多少字节
sk.sendall('111') #服务端那边隔断
print(result_len)
data = bytes() #bytes为空的变量
while len(data) != result_len:
recv = sk.recv(1024)
data += recv
print(str(data,'gbk'))
#为什么用gbk 应为服务端执行subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)命令时 Windows默认gbk
sk.close()
4、socketserver 实现 服务端 客户端 实现并发一个效果
虽然用Python编写简单的网络程序很方便,但复杂一点的网络程序还是用现成的框架比较好。这样就可以专心事务逻辑,而不是套接字的细节。
socketserver模块简化了编写网络服务程序的任务。同时socketserver模块也是Python标准库中很多服务器框架的基础
socketserver模块可以简化网络服务器的编写,Python把网络服务抽象成两个主要的类,一个是Server类,用于处理连接相关联的网络操作,另外一个则是RequestHandler类,用于处理数据相关的操作。并且提供两个MixInl类,用于扩展Server,实现多进程多线程。
服务端
#实现并发
import socketserver
# Myserve 类名自己定义 括号里内容是固定的
class Myserve(socketserver.BaseRequestHandler):
def handle(self): #固定的父类方法 要重写父类方法 必须用handle
print('服务端启动。。。')
while True:
conn = self.request
#通过self.request 去拿conn 也可以直接用这个方法 但是不方便
#self.request内部做了个调用 做了个变量赋给request 就是客户端传过来的sk
print(self.client_address)
while True:
client_data = conn.recv(1024) #一收
print(str(client_data,'utf8')) #打印接受内容
print('waiting..')
server_response = input('>>>') #自己输入的 下面在发过去 #可以来回沟通
conn.sendall(bytes(server_response,'utf8')) #
#conn.sendall(client_data)
conn.close()
if __name__ =='__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8091),Myserve) #ip地址 和自己设置的类名字
server.serve_forever() #方法 通过它启动 handle 方法
#socketserver 调用方法 ThreadingTCPServer 多线程TCP的服务类 为了实现并发
客户端
import socket
ip_port = ('127.0.0.1',8091)
sk = socket.socket()
sk.connect(ip_port)
print('客户端启动:')
while True:
inp = input('>>>')
sk.sendall(bytes(inp,'utf8'))
if inp =='exit':
break
serve_response = sk.recv(1024)
print(str(serve_response,'utf8'))
sk.close()
5、线程:threading模块
是操作系统能够进行运算调度的最小单位。它被包含在进程只之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多线程,每条线程并执行不同的任务。
线程说白了就是一堆指令集
print('ok')
执行它 就是调度操作系统 去让cpu工作执行这个代码内容
能让操作系统工作起来的,做小的一个单位就是线程
线程和进程注意的点是: 线程可以资源共享的 进程之间是不可以哒
进程和线程是一样快的
IO密集型任务或函数: 有阻塞的状态,不会一直用cpu,中间会后等待,告诉cpu我这先不用执行了有这些状态叫IO密集型
计算密集型任务函数:没有上述状态的 或少的就是计算密集型
Python没有多线程,因为有gIL锁:在同一时刻,只能有一个线程近入解释器。
官法介绍 gIL锁(在C Python里面由于你的全局解释器锁的一个存在,同一时刻,只能有一个线程被执行,如果你想利用你的多核,建议你使用multiprocessing 一个多进程的模块,然而你处理的任务是是IO密集型,我的多线程还是非常合适的模式)
但是有很多的替代方案 进程 协程+多进程 以后会学
总结:在Python里:
如果任务是IO密集型的 可以用多线程
是计算密集型的,对不起。改C语言是不错的选择,如果不会C python有多进程什么的
简单并发的例子
"""
并行是正真意义上的两件事情同时干
我们这个例子并不是两件事情同时干 是并发的切换 那怎么出来这个效果的呢 应为有sleep
seelp时候cpu并不会被占用 这个时候它可以解决其他问题
就类似于 电脑看电影时候也可以放音乐 cpu是在他俩之间来回切换的 音乐执行一会 电影执行一会 速度很快用户是感觉不到的
下面这个例子也是一样 不管是foo 还是bar 总有一个是先抢到执行打印然后(假设是foo) sleep住 去换bar 发现它也sleep住了
两个都停住了 然后foo8秒结束 接着bar也就结束 两个共同8秒
#多线程
"""
import time
import threading #Python创建线程的模块
#这一堆线程叫主线程 主线程里创建一些子线程
begin = time.time()
def foo(n):
print('foo%s'%n)
time.sleep(8)
print('end foo')
def bar(n):
print('bar%s'%n)
time.sleep(8)
print('end bar')
#
#这么创建子线程threading.Thread创建线程,为了让他执行一个任务,就是执行某一个函数
#有个参数target =后面跟你要处理的函数名字,只是函数名字 ,加括号就执行了 不加 ,逗号
#加个参数 args = 后面是元组的形式 。。这样一句话就相当于创建一个线程对象
t1 = threading.Thread(target = foo,args = (1,)) #这个线程自己的任务执行foo
t2 = threading.Thread(target=bar,args=(2,)) #这个线程自己的任务执行bar
#主线程从上到下执行代码
#三个谁快谁慢 是一种抢占式的
#抢占cpu 通过 os
t1.start() #想让跑起来.start()
t2.start()
print('......in the main...........')
t1.join() #加一个方法 不加的话 主程序执行完 时间直接就打印了 不等两个线程执行完
t2.join()
#你的 t1 t2 两个子线程不结束,不往下走,卡在这里 所以就能看到整个程序执行多长时间了
end = time.time()
print(end-begin)
setDaemon()守护线程方法 加主线程上 主线程执行完 不等子线程 程序直接结束 加子线程上也同理
Python线程进程的区别:
1、线程们之间是共用的 一个进程的内存空间,进程是一个资源的整合。进程之间是独立的内存空间资源不能共享 就好比 QQ 和360 他两里面的资源是不能共享的
2、说白了就是一个进程里面有一个变量 他的子线程都可以去操作,假如里面有一个 123 ,第一个线程进行操作变成223了 第二个线程取得时候就成223了
3、线程之间可以通讯 进程之间不可以。
4、新的线程很容易被创建,进程不容易,因为开销大,你的一个新的进程的创建需要拷贝他的父亲的进程,完全拷贝一份。
5、线程之间可以被互相操作 进程不可以
6、线程有主线程和子线程,主线程可以影响子线程,进程有主进程 和子进程,主进程一点不能影响子进程 ,进程完全相互独立
6、同步锁:
先看:
# 同步锁 前戏
import threading
def addnum():
global num #在每个线程中获取这个全局变量
num -= 1 #2、对num减一 减到0 得100次 太慢 开线程得开100个线程 通过一个 for循环
num = 100 #1、有一个全局的变量 数字100 要让他做一个减1的动作
thread_list = []
for i in range(100): #3、通过for循环创建一个多线程 循环100次
t = threading.Thread(target=addnum)
t.start() #4、创建了100个t 运行起来执行addnum函数
thread_list.append(t) #5、然后把一百个对象加到列表里面,目的是对
#100对象进行一个join
for t in thread_list:
t.join()
print('final num:', num) #6、让他最后打印 看看最后的num是多少
#结果是0
接下来 看一个现象:现象就是num -= 1 做拆分 中间加了句代码 结果就不一样了
import threading
def addnum():
global num
temp = num #1、拿一个中间变量 叫temp接受num
print('ok')
# print('123') #加这个效果更明显点
num = temp - 1 #2、让temp减一 和之前效果是一样的
#3、然后他俩中间加一个 print('ok') 结果有时候是0 有时候不是0,0的时候多 什么原因 在下面
#2、temp = num num = temp - 1 虽然是一样的 但是多了一步运算,cpu来回切换的时间就多了,
num = 100
#3 的答案、100作为一个全局变量,100线程可以同时操作它,虽然有个先后但是时间非常少,线程拿到100以后都想做一个减一的操作
#比如第一个线程首先拿到100了,第一步执行 上面的temp = num ,print('ok') 执行完这两句代码的时候cpu是有可能切换的
#取了个100打印了个ok 切换到别的线程上了 第二个线程过来 取得还是100的值,但是我们是想让他取99呢,这叫线程不安全
#直接 num -= 1 是不会出现那种情况的 不到cpu切换呢还,切换之前就把活干完了
#num -= 1 一拆分 就会影响结果
#为什么有时候是1 0 4 7 这几个数呢 这就数字几就代表被覆盖了几次 按理说 都是一样 都是公平的 都是100个线程干的一件事
#怎么会 有的切有的不切,还是因为时间短 中间加time.sleep(0.1)就都是99了
thread_list = []
for i in range(100): #3、通过for循环创建一个多线程 循环100次
t = threading.Thread(target=addnum)
t.start() #4、创建了100个t 运行起来执行addnum函数
thread_list.append(t) #5、然后把一百个对象加到列表里面,目的是对
#100对象进行一个join
for t in thread_list:
t.join()
print('final num:', num) #6、让他最后打印 看看最后的num是多少
怎么解决上面问题 就是加锁
# 怎么解决上面问题呢 买吧锁 让cpu执行完在切换
#关键怎么枷锁 看下面
import threading
import time
def addnum():
global num
r.acquire() #给cpu加锁 不会再干别的(想获取这把锁)
temp = num
time.sleep(0.0001)
num = temp - 1
r.release() #上面代码执行完了 释放
num = 100
thread_list = []
r = threading.Lock() #买到锁 叫r 在上面加
for i in range(100):
t = threading.Thread(target=addnum)
t.start()
thread_list.append(t)
for t in thread_list:
t.join()
print('final num:', num)
#锁的那一步份是线程 锁里面的内容是串行执行 其他还是并发 因为你的代码还可以加别的东西
上面代码阻塞怎么不用join 因为join就把整个进程停住了 失去了多线程的意义 变成串行了
死锁 :要避免死锁的出现
死锁 什么现象呢 卡住不动了 执行几条以后
#为什么会出现死锁 当有一个线程先抢到A方法 它自己执行完 去执行B方法的时候 第二条线程就可以执行A去了
#当第一条线程执行B方法第一条结束 想要用A锁时候 A锁让第二条线程占着呢
#所有两人就锁死了 就造成你想要我的锁 我想要你的锁 没有外力的推动 他俩就死这了
import threading,time
class myThread(threading.Thread):
def doA(self): #A在里面做了两件事 打印
lockA.acquire() #A加锁
print(self.name,"gotiockA",time.ctime())
time.sleep(3)
lockB.acquire() #B加锁
print(self.name,"getlockB",time.ctime())
lockB.release() #B释放
lockA.release() #A释放
def doB(self): #B和上面一样
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): #3、先有一个人搞到了A
self.doA()
self.doB()
if __name__=='__main__':
lockA = threading.Lock() #1、首先我创建了两把锁
lockB = threading.Lock()
threads = []
for i in range(5): #2、开了5个线程
threads.append(myThread()) #创建一个类myThread 放到列表
for t in threads: #通过循环起动里面方法 五个线程同时去执行里面的run方法
t.start()
for t in threads:
t.join()
解决方法:改成一把锁 递归锁RLock
#怎么解决 一把锁 递归锁 可以多次被获取 里面有一个计算器 获取时候(-) 释放时候(+)
#内部相当于一个字典 一把锁对应一把钥匙 对应的
import threading,time
class myThread(threading.Thread):
def doA(self):
lock.acquire()
print(self.name,"gotiockA",time.ctime())
time.sleep(3)
lock.acquire()
print(self.name,"getlockB",time.ctime())
lock.release()
lock.release()
def doB(self):
lock.acquire()
print(self.name,'gotlockB',time.ctime())
time.sleep(2)
lock.acquire()
print(self.name,'gotlockA',time.ctime())
lock.release()
lock.release()
def run(self):
self.doA()
self.doB()
if __name__=='__main__':
#lockA = threading.Lock() #改成了一把锁
#lockB = threading.Lock()
lock = threading.RLock() #递归锁
threads = []
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()
信号量: 也是锁
信号量用来控制线程并发数的,BoundedSemaphore 或 Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。
BoundedSemaphore 与 Semaphor的唯一区别在于前者在调用release()时检查计数器的值是否超过了计数器的初识值,如果超过了将抛出一个异常
例子:
#信号量 也是一把锁 类似于生活里的停车场 可以自己设定 车位 固定几个 有位置才可以进 控制多少线程同时进入
#设定5个 5个5个的出 看起来是一样的 但是也是竞争的走呢
#有什么用呢 比如数据库 我想进去拿数据 改数据 得和数据库连接 那如果100个线程同时 进去受不了就可以限制一下
#同时有几个去连接数据库
import threading,time
class myThread(threading.Thread): #threading.Thread创建线程的方法
def run(self):
if semaphore.acquire(): #有没有if一样
print(self.name)
time.sleep(3)
semaphore.release()
if __name__ == '__main__':
semaphore = threading.BoundedSemaphore(5) #创建一把锁 能自己设定个
thrs = []
for i in range(100):
thrs.append(myThread())
for t in thrs:
t.start()
条件变量:Condition 也是锁,最复杂的一把锁,除了锁以外,他还能实现的功能叫:线程间的通信类似于。
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了wait(),notify(),notifyAII()方法。
lock_con=threading.Condition([Lock/Rlock]):锁是可选项,不传入人锁,对象自动创建一个RLock()。
wait():条件不满足时调用,线程会释放锁并进入等待阻塞;
notify():条件创造后调用,通知等待池激活一个线程;
notifyAII():条件创造后调用,通知等待池激活所有线程。
例子
#生产者 做包子的 消费者 吃包子的 生产包子和做包子的一块走 但是满足一个条件
#吃包子的的人先得满足有包子 这个例子主要讲Condition实现这个条件呢
import threading,time
from random import randint
class Prodycer(threading.Thread):
def run(self):
global L #3、L一个空列表 类似放包子的大屉 做好包子就放里面
while True:
val = randint(0,100) #4、randint随机模块 创建一个包子
print('生产者',self.name,"Append"+str(val),L)
if lock_con.acquire(): #5、紧接着 获取一个锁
L.append(val) #6、往L里面放包子
lock_con.notify() #8、告诉wait有包子了 可以吃了 激活
lock_con.release()
time.sleep(3)
#有可能六个线程跑起来 消费者先获得了 那把锁(两人是一把锁) 获得那把锁不能做任何操作 因为没有包子
class Consumer(threading.Thread):
def run(self):
global L
while True:
lock_con.acquire() #7、看消费者 也创建一把锁
if len(L)==0: #做个判断如果是 0
lock_con.wait() #wait两个作用 1、先把锁释放了因为这把锁归消费者所有 生成者需要这把锁 然后阻塞一直到有人通知我
print('消费者',self.name,":Delete"+str(L[0]),L)
del L[0] #吃包子 第一个 索引 0
lock_con.release()
time.sleep(1) #一秒吃 第三秒时候 上面做
if __name__ == '__main__':
L = []
lock_con = threading.Condition() #1、创建一把条件变量的锁
threads = []
for i in range(5): #2、之后启动线程 5个生产者
threads.append(Prodycer())
threads.append(Consumer()) #又添加了一个对象消费者 现在有6个线程对象 5个做1个吃
for t in threads:
t.start() #然后启动 下面阻塞
for t in threads:
t.join()
print('----------chibaole-----------')
7、同步条件 Event(事件)
条件同步和条件变量同步,差不多意思,只是少了锁的功能,因为条件同步设计于不访问共享资源的条件环境。event = threading.Event():条件环境对象,初始值为False;
也可以进行线程间的交互,只不过不要锁了。就是说我没有公共的数据要操作,但是我也想进行通信,这个时候就可以用它Event(事件) 比,条件变量:Condition 用的更广。有自己的方法,
event.isSet(): 返回event的状态值
event.wait():如果event。isSet == False 将阻塞线程;
event.set():设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度
event.clear()恢复event的状态值为False
event里面自己有一个标志位,就是一变量,变量叫什么都行 变量是里面自己写的
决定了什么 决定了如果你调用event.wait(),.wait()就是判断你这个标志位,如果False的话 就把你这个线程给阻塞住。如果等于True的话就会继续往下走 。通过谁来改呢 通过函数来改(在内部呢 只能通过函数改)event.set(): event.clear()
下面看个例子 需求 老板晚上说加班 工人就说命苦 (工人说命苦得在老板说完加班以后再说)所以就有一个先后了
import threading,time
class Boss(threading.Thread):
def run(self):
print("BOSS: 今晚大家都要加班到22:00.") #5、
event.set() #6、说完加班,#event.set 设置event的状态值为True,
#往下走
time.sleep(5) #7、然后执行等待5秒 同时 下面员工执行 跟着喊命苦
print("BOSS:<22:00>可以下班了。")
event.set() #10、又设置 下面可以欢呼了
#4、假如 Worker先抢到cpu 它不能喊命苦得wait() 等老板先说话 说完加班 然后释放wait()就可以继续走
#
class Worker(threading.Thread):
def run(self):
event.wait()
print("Worker: 命苦!!!") #8、
time.sleep(1) #9、一秒钟以后
event.clear() #然后把内部的标志位设置成False 相当于老板的线程再拿标志位时候就是False了
event.wait() #然后evet.isSet()==False 自己wait()住 等老板在说话 说可以下班了,然后上面又设置
print("Worker:ohYesh!")
if __name__ == "__main__":
event = threading.Event() #1、event拿到这个事件
threads = []
for i in range(5): #2、启动5个 Worker()
threads.append(Worker())
threads.append(Boss()) #3、启动一个老板 执行上面 先打印加班没有问题
for t in threads:
t.start()
for t in threads:
t.join()
8、多线程利器 queue
队列:是一个数据结构
怎么创建一个队列,引入一个模块queue
import queue #引入队列
d = queue.Queue(3) #创建一个队列对象(后面跟得参数是,控制你可以插入几条数据)里面不加参数默认是0无限大
d.put('sb') #有了队列对象可以在里面放数据了 (元组,列表意思差不多)用put方法 插入
d.put('sx') #插入3个数据
d.put('nb')
# d.put('nb',0)
#put 里面有参数 有默认的布尔值 ,逗号加0 提示报错 1 阻塞
#get取出
print(d.get()) #取得时候 有顺序的 先进先出 顺序的名字叫FIFO(默认的)也有先进后出,还可以按优先级来
print(d.get())
print(d.get())
print(d.get(0)) #里面直接加0
#有什么好处呢?
#多线程的时候用列表 就造成线程不安全,(就是哪个线程过来都可以拿数据)
#多线程时候用(队列内部自己有一把锁,不管时间有多短,一个线程只能拿到一个数据)
简单的引用:吃包子例子
import threading,queue
from time import sleep
from random import randint
class Production(threading.Thread):
def run(self):
while True:
r = randint(0,100)
q.put(r) #开了三个线程,不会同时都插入的相同的数据产生矛盾
print("生产出来%s号包子"%r)
sleep(1)
class Proces(threading.Thread):
def run(self):
while True:
re = q.get() #
print('吃掉%s号包子'%re)
if __name__ == "__main__":
q = queue.Queue(10) #1、创建了一个队列对象
threads = [Production(),Production(),Production(),Proces()]
#2、创建了4个对象 三个生产 一个吃
for t in threads:
t.start()
#其他三个方法
# q.qsize() #返回队列大小 ,(看看队列里还有多少数据,返回数字)
# q.full():#如果队列满了返回True,否则为False
# q.empty()#如果队列为空返回True,否则为False
#(他两是做一个判断的,达到某种临界的时候得需要判断,此时此刻我的队列是是不是为空了,如果为空了怎么办,如果满了怎么办)
9、进程:
执行的一个程序就叫一个进程 ,QQ就是一个进程,里面有多个线程 里面资源是共享的
由于GIL的存在,Python中的多线程其实并不是真正的多线程,如果想要充分的使用多核cpu的资源,在Python中大部分情况需要使用多线程。在Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有的事情。借助这个包可以轻松的完成从单进程到并发执行的转换。
multiprocessing支持子进程、通讯和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start() run() join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类(这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部分与threading使用同一套API,只不过换到了多进程的情境
但在使用这些共享API的时候,我们需要注意以下几点;
1、在UNIX平台上,当每个进程终结之后,改进程需要被其父进程调用wait,否则进程将成为,僵尸进程(zombie)。所以,有必要对每个Process对象调用join()方法(实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在必要性。
2、multiprocessing提供了threading包中没有的IPC(比如pipe和queue),效率上更高。应优先考虑pipe和queue
避免使用Lock/Event/Semaphore/Condition等同步方式(因为他们占据的不是用户进程的资源)。
3、多进程应该避免共享资源。多线程中,我们比较容易的共享资源,比如使用全局变量或传递参数。在多进程下,由于每个进程有自己独立的内存空间,已上方法并不适合。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。
Process.PID中保存有PID,如果进程还没有start(),则PID为None。
Window 系统下,需要注意的是要想启动一个子程序,必须加上那句if __name == "__main__",进程相关的要写在这句下面。
进程创建:
from multiprocessing import Process
#multiprocessing 进程模块 引入一个方法Process 为了以后调用输这个Process
#要不然还得multiprocessing.Process (这样调用)
import time
def f(name):
time.sleep(1)
print('hello',name,time.ctime())
if __name__ == "__main__":
p_list = []
for i in range(3):
p = Process(target=f,args=('wk',)) #1、创建进程对象(里面函数名 和参数)
p_list.append(p)
p.start() #2、还是和线程一样
for p in p_list:
p.join()
print('end')
类调用 进程
from multiprocessing import Process
import time
class MyProcess(Process): #继承方法
def __init__(self):
super(MyProcess,self).__init__()
def run(self):
time.sleep(1)
print('hello',self.name,time.ctime())
if __name__ == "__main__":
p_list = []
for i in range(3):
p = MyProcess()
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('end')
进程关系
from multiprocessing import Process
import os
import time
def info(title):
print(title) #你传什么打印什么
print('module name:',__name__) #主进程打印main
print('parent process:',os.getppid()) #父进程的进程号
print('process id:',os.getppid()) #本进程号 os方法
if __name__ == "__main__":
info('\033[32;lmmain process line line\033[0m') #带颜色的lmmain process line(我的显示不了绿色)
time.sleep(3)
p = Process(target=info,args=('bob',)) #也去执行info 传一个参数
p.start()
p.join()
进程间的通信 queue(队列) pipe(管道)
进程队列:
最主要的就是把队列q作为一个参数要传给f达到通讯的目的
from multiprocessing import Process, Queue #队列模块也得引过来
def f(q,n):
q.put([42,n,'hello']) #put一个数据
print('subprocess id:',id(q)) #查看id存储位置 发现q是拷贝的 不是共用
if __name__ == "__main__":
q = Queue() #1、直接 创建了一个队列
p_list = []
print('main q id:', id(q)) #查看id存储位置 发现q是拷贝的 不是共用
for i in range(3): #三个进程 分别执行f方法
p = Process(target=f,args=(q,i)) #参数q 就是q = Queue() 传给子进程 然后f才可以调用q.put
p_list.append(p)
p.start()
print(q.get())
print(q.get())
print(q.get())
for i in p_list:
i.join()
管道 pipe
from multiprocessing import Process,Pipe
def f(conn):
conn.send("约吗???")
conn.send("约吗???")
print(conn.recv())
conn.close()
if __name__ == "__main__":
parent_conn, child_conn = Pipe() # pipe()拿到两个(结果)管道 给子进程一个父进程一个
p = Process(target= f,args=(child_conn,))
# child_conn子进程作为一个参数 传给子进程
p.start()
print(parent_conn.recv()) #阻塞住 一直等 上面上面执行 发送数据 这接收
print(parent_conn.recv())
parent_conn.send('好呀!!!')
p.join()
浙公网安备 33010602011771号