Socket
socket通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用【打开】【读写】【关闭】模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
socket和file的区别:
file模块是针对某个指定文件进行【打开】【读写】【关闭】
socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
#socket基本举例(服务端)
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)
while True:
conn, address = sk.accept()
conn.sendall(bytes('hello', encoding='utf-8'))
while True:
recv_bytes = conn.recv(1024)
if str(recv_bytes,encoding='utf-8') == 'q':
break
conn.sendall(recv_bytes)
#socket基本举例(客户端)
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 9999,))
recv_bytes = sk.recv(1024)
recv_str = str(recv_bytes, encoding='utf-8')
while True:
inp = input('>>>')
sk.sendall(bytes(inp, encoding='utf-8'))
if inp == 'q':
break
print(str(sk.recv(1024),encoding='utf-8'))
#WEB服务应用:
import socket
def handle_request(client):
buf = client.recv(1024)
client.send("HTTP/1.1 200 OK\r\n\r\n")
client.send("Hello, World")
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost',8080))
sock.listen(5)
while True:
connection, address = sock.accept()
handle_request(connection)
connection.close()
if __name__ == '__main__':
main()
socket模块
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) #创建socket对象
参数一:地址簇
socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6
socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
参数二:类型
socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , for UDP
socket.SOCK_RAW
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_SEQPACKET 可靠的连续数据包服务
参数三:协议
0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
#UDP Demo
#Server
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)
while True:
data = sk.recv(1024)
print data
#Clinet
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
inp = raw_input('数据:').strip()
if inp == 'exit':
break
sk.sendto(inp,ip_port)
sk.close()
sk.bind(address)
s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
sk.listen(backlog)
开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
这个值不能无限大,因为要在内核中维护连接队列
sk.setblocking(bool)
是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。主要用户IO多路复用
sk.accept()
接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
接收TCP 客户的连接(阻塞式)等待连接的到来
coon,address = sk.accept()
sk.connect(address)
连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
sk.connect_ex(address)
同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
sk.close()
关闭套接字
sk.recv(bufsize[,flag])
接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
sk.recvfrom(bufsize[.flag])
与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
sk.send(bytes[,flag])
将string(byte)中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
补充内置函数:bytes() #把字符串可以转换成字节类型,想把一个字符串转换成一个字节。
bytes("hello",encoding="utf8")
bytes("hello","utf8")
sk.sendall(bytes[,flag])
将string(byte)中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
内部通过递归调用send,将所有内容发送出去。
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.getpeername() #常用于客户端
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname() #常用语服务端
返回套接字自己的地址。通常是一个元组(ipaddr,port)
sk.fileno()
套接字的文件描述符
socket要点
1.python3.x版本的socket只能收发字节(python2.7可以发送str)
2.退出只在客户端退出就ok了
3.s.accept()和s.recv()是阻塞的(基于链接正常)
4.listen(n) n代表:能挂起的链接数,如果n=1,代表可以链接一个,挂起一个,第三个拒绝
5.服务端出现端口冲突:修改监听端口号
send和sendall区别:
sendall发消息的时候时候是调用send的sendall比send更强大
rst = send(bytes("asd",encoding="utf-8")) #可以获取send一次发了多少字节
sendall 相当于在内部启用一个while循环,由于send发送消息时有时未能发送全,所以推荐使用sendall
粘包解决:
出现粘包问题主要是由于socket在传输的时候,例如在利用以太网协议传输的时候,超出以太网最大包数(以太网协议中,除去首部,数据部分不得超过1500字节)
服户端:
1.send #数据长度
4.recv #收到确认信息,开始下一步发送
send
客户端:
2.recv #获取数据长度
3.send #发送确认信息
recv #循环接收
IO多路复用
I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
Linux中的 select,poll,epoll 都是IO多路复用的机制。
select
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll
poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
epoll
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
各操作系统对io多路复用的支持
Windows Python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll
注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。
对于select方法:
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)
参数: 可接受四个参数(前三个必须)
返回值:三个列表
select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
1.当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
2.当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
3.当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
4.当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
#select举例
import socket
import select
sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)
inputs = [sk,]
outputs = []
messages = {}
# del messages[walker]
# messages = {
# walker:[消息1,消息2,]
# wang:[消息1,消息2,]
# }
while True:
rlist,wlist,elist, = select.select(inputs, outputs,[sk,],1)
#rlist 读列表
#wlist 写列表
#elist 错误列表
print(len(inputs),len(rlist),len(wlist), len(outputs))
# 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为【sk】
# 监听conn对象,如果conn发生变化,表示客户端有新消息发送过来了,此时rlist的之为 【客户端】
# rlist = 【wang,】
# rlist = 【tom,walker,】
# rlist = [sk,]
for r in rlist:
if r == sk:
# 新客户来连接
conn, address = r.accept()
# conn是什么?其实socket对象
inputs.append(conn)
messages[conn] = []
conn.sendall(bytes('hello', encoding='utf-8'))
else:
# 有人给我发消息了
print('=======')
try:
ret = r.recv(1024)
# r.sendall(ret)
if not ret:
raise Exception('断开连接')
else:
outputs.append(r)
messages[r].append(ret)
except Exception as e:
inputs.remove(r)
del messages[r]
# 所有给我发过消息的人
for w in wlist:
msg = messages[w].pop()
resp = msg + bytes('response', encoding='utf-8')
w.sendall(resp)
outputs.remove(w)
#!/usr/bin/env python
#coding:utf8
'''
服务器的实现 采用select的方式
'''
import select
import socket
import sys
import Queue
#创建套接字并设置该套接字为非阻塞模式
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(0)
#绑定套接字
server_address = ('localhost',10000)
print >>sys.stderr,'starting up on %s port %s'% server_address
server.bind(server_address)
#将该socket变成服务模式
#backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
#这个值不能无限大,因为要在内核中维护连接队列
server.listen(5)
#初始化读取数据的监听列表,最开始时希望从server这个套接字上读取数据
inputs = [server]
#初始化写入数据的监听列表,最开始并没有客户端连接进来,所以列表为空
outputs = []
#要发往客户端的数据
message_queues = {}
while inputs:
print >>sys.stderr,'waiting for the next event'
#调用select监听所有监听列表中的套接字,并将准备好的套接字加入到对应的列表中
readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字 如果是文件呢?
#监控文件句柄有某一处发生了变化 可写 可读 异常属于Linux中的网络编程
#属于同步I/O操作,属于I/O复用模型的一种
#rlist--等待到准备好读
#wlist--等待到准备好写
#xlist--等待到一种异常
#处理可读取的套接字
'''
如果server这个套接字可读,则说明有新链接到来
此时在server套接字上调用accept,生成一个与客户端通讯的套接字
并将与客户端通讯的套接字加入inputs列表,下一次可以通过select检查连接是否可读
然后在发往客户端的缓冲中加入一项,键名为:与客户端通讯的套接字,键值为空队列
select系统调用是用来让我们的程序监视多个文件句柄(file descrīptor)的状态变化的。程序会停在select这里等待,
直到被监视的文件句柄有某一个或多个发生了状态改变
'''
'''
若可读的套接字不是server套接字,有两种情况:一种是有数据到来,另一种是链接断开
如果有数据到来,先接收数据,然后将收到的数据填入往客户端的缓存区中的对应位置,最后
将于客户端通讯的套接字加入到写数据的监听列表:
如果套接字可读.但没有接收到数据,则说明客户端已经断开。这时需要关闭与客户端连接的套接字
进行资源清理
'''
for s in readable:
if s is server:
connection,client_address = s.accept()
print >>sys.stderr,'connection from',client_address
connection.setblocking(0)#设置非阻塞
inputs.append(connection)
message_queues[connection] = Queue.Queue()
else:
data = s.recv(1024)
if data:
print >>sys.stderr,'received "%s" from %s'% \
(data,s.getpeername())
message_queues[s].put(data)
if s not in outputs:
outputs.append(s)
else:
print >>sys.stderr,'closing',client_address
if s in outputs:
outputs.remove(s)
inputs.remove(s)
s.close()
del message_queues[s]
#处理可写的套接字
'''
在发送缓冲区中取出响应的数据,发往客户端。
如果没有数据需要写,则将套接字从发送队列中移除,select中不再监视
'''
for s in writable:
try:
next_msg = message_queues[s].get_nowait()
except Queue.Empty:
print >>sys.stderr,' ',s,getpeername(),'queue empty'
outputs.remove(s)
else:
print >>sys.stderr,'sending "%s" to %s'% \
(next_msg,s.getpeername())
s.send(next_msg)
#处理异常情况
for s in exceptional:
for s in exceptional:
print >>sys.stderr,'exception condition on',s.getpeername()
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
del message_queues[s]
SocketServer模块
SocketServer内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。
ThreadingTCPServer
ThreadingTCPServer实现的Soket服务器内部会为每个client创建一个 “线程”,该线程用来和客户端进行交互。
ThreadingTCPServer基础
使用ThreadingTCPServer:
创建一个继承自 SocketServer.BaseRequestHandler 的类
类中必须定义一个名称为 handle 的方法
启动ThreadingTCPServer
#SocketServer示例
import sockerserver #服务端
class MyServer(sockerserver.BaseRequestHandler):
def handle(self): #必须要写handel方法,因为父类里有一个空的handel方法
# print self.request,self.client_address,self.server
conn = self.request #conn
conn.sendall(bytes('欢迎致电 10086,请输入1xxx,0转人工服务.'),encoding= "utf8")
Flag = True
while Flag:
data = conn.recv(1024)
if data == 'exit':
Flag = False
elif data == '0':
conn.sendall('通过可能会被录音.balabala一大推')
else:
conn.sendall('请重新输入.')
if __name__ == '__main__':
server = sockerserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer) #把类导入进来
server.serve_forever() #服务器永远运行,不断的等待新链接,来一个链接分一个新线程
import socket #客户端
ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)
while True:
data = sk.recv(1024)
print 'receive:',data
inp = input('please input:')
sk.sendall(inp)
if inp == 'exit':
break
sk.close()
sockerserver.BaseRequestHandler):
self.setup() 实例将自动线执行此方法
self.handle() 实例处理的方法 #很重要!
self.finish() 实例处理完最后结束的方法
sockerserver.BaseRequestHandler 主要方法
Request Handler Objects
class socketserver.BaseRequestHandler
This is the superclass of all request handler objects. It defines the interface, given below. A concrete request handler subclass must define a new handle() method, and can override any of the other methods. A new instance of the subclass is created for each request.
setup()
Called before the handle() method to perform any initialization actions required. The default implementation does nothing.
handle()
This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.
The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.
finish()
Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.
ThreadingTCPServer源码剖析
ThreadingTCPServer的类图关系如下:
内部调用流程为:
1.启动服务端程序
2.执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
3.执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 4.MyRequestHandle赋值给 self.RequestHandlerClass
5.执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
6.当客户端连接到达服务器
7.执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
8.执行 ThreadingMixIn.process_request_thread 方法
9.执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 10.MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)
SocketServer的ThreadingTCPServer之所以可以同时处理请求得益于 select 和 Threading 两个东西,其实本质上就是在服务器端为每一个客户端创建一个线程,当前线程用来处理对应客户端的请求,所以,可以支持同时n个客户端链接(长连接)。
队列模块
python所有队列
先进先出(普通队列):queue.Queue
后进先出:queue.LifoQueue
优先级队列:queue.PriorityQueue
双向队列:queue.deque
# q = queue.LifoQueue()
# q.put(123)
# q.put(456)
# print(q.get())
# q = queue.PriorityQueue() #先拿优先级大的来,0是最大!其次是1 2 3 4
# q.put((1,"alex1"))
# q.put((1,"alex2"))
# q.put((1,"alex3"))
# q.put((3,"alex3"))
# print(q.get())
# q = queue.deque()
# q.append(123)
# q.append(333) #放右边
# q.appendleft(456) #放左边
# q.pop() #取右边
# q.popleft() #取左边
Python线程
Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。
#线程的小知识:
1、一个应用程序,可以有多进程和多线程,默认是单进程和单线程
2、GIL,全局解释器锁
在利用CPU计算操作的时候会启动
3、单进程、多线程
多线程:IO操作 python和java还有C#一样
多线程:计算操作 python效率不如java和C#因为,python有GIL
4、python IO操作用 多线程提高并发
5、python 计算性操作用 多进程提高并发
threading方法
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
join 逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
run 线程被cpu调度后自动执行线程对象的run方法
如何创建线程
#自定义创建线程1:
import threading
def f1(a)
t=threading.Thread(target=f1,args(123,)) #创建一个线程,并运行f1函数,元组内是传给f1的参数
t.start() #不代表当前线程会被立即执行,线程启用遵循CPU的调度方法
t.setDaemon(True) #如果是True 表示主线程不等子线程
t.jion() #相当于中断,主线程执行到此中断,先运行子线程,然后再允许主线程,
t.jion(2) #主线程最多等2秒,如果子线程运行完,就以子线程运行时间为准,如果没运行完,就按照参数最多等待
t.add_thread #增加一个线程
#自定义创建线程2:
t.start()是调用threading.Thread的run()方法。所以可以自己创建一个类继承threading.Thread类
import threading
class MyThread(threading.Thread):
def __init__(self, func,args):
self.func = func
self.args = args
super(MyThread, self).__init__() #主动调用一下父类的方法
def run(self):
self.func(self.args)
def f2(arg):
print(arg)
obj = MyThread(f2,123)
obj.start()
线程锁
用于多个线程共享同一个数据。
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。
import threading
import time
NUM = 10
def func(i,l):
global NUM
# 上锁
l.acquire() # 30,5 25m5,20
NUM -= 1
time.sleep(2)
print(NUM,i)
# 开锁
l.release()
# lock = threading.Lock() #只支持单层锁,不支持多层锁嵌套
# lock = threading.RLock() #支持多层锁嵌套,常用
lock = threading.BoundedSemaphore(5)#最多允许出来5个
for i in range(30):
t = threading.Thread(target=func,args=(i,lock,))
t.start()
event = threding.Event() #event可以批量等待或者放行
event.wait() #检测标识
event.set() #放行标识,设置成绿灯
event.clear() #等待标识,设置成红灯
条件及定时器
条件(Condition)
使得线程等待,只有满足某条件时,才释放n个线程
#方法1
import threading
def func(i,con):
print(i)
con.acquire()
con.wait()
print(i+100)
con.release()
c = threading.Condition()
for i in range(10):
t = threading.Thread(target=func, args=(i,c,))
t.start()
while True:
inp = input('>>>')
if inp == 'q':
break
c.acquire() #acquire,notify,release先后顺序固定
c.notify(int(inp)) #notify(整数) 一次放出多少个
c.release()
#方法2
import threading
def condition():
ret = False
r = input('>>>')
if r == 'true':
ret = True
else:
ret = False
return ret
def func(i,con):
print(i)
con.acquire()
con.wait_for(condition) #把condition函数的return结果返回
print(i+100)
con.release()
c = threading.Condition()
for i in range(10):
t = threading.Thread(target=func, args=(i,c,))
t.start()
Timer
定时器,指定n秒后执行某操作
from threading import Timer
def hello():
print("hello, world")
t = Timer(1, hello)
t.start() # after 1 seconds, "hello, world" will be printed
自定义线程池:
#low比版本:没有线程重用功能
import queue
import threading
import time
class ThreadPool:
def __init__(self, maxsize=5):
self.maxsize = maxsize
self._q = queue.Queue(maxsize)
for i in range(maxsize):
self._q.put(threading.Thread) #添加5个threading.Thread类
# 【threading.Thread,threading.Thread,threading.Thread,threading.Thread,threading.Thread】
def get_thread(self):
return self._q.get()
def add_thread(self):
self._q.put(threading.Thread)
pool = ThreadPool(5)
def task(arg,p):
print(arg)
time.sleep(1)
p.add_thread()
for i in range(100):
# threading.Thread类
t = pool.get_thread() #调用pool对象中的get_thread方法,获取一个线程队列的类
obj = t(target=task,args=(i,pool,)) #得到线程对象
obj.start()
#高大上版本(python2.7写的)--主要思想:把任务放入队列(可以把任务(函数名),参数封装成元祖或列表)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import queue
import threading
import contextlib
import time
StopEvent = object()
class ThreadPool(object):
def __init__(self, max_num, max_task_num = None):
if max_task_num: #先判断任务数
self.q = queue.Queue(max_task_num)
else:
self.q = queue.Queue()
self.max_num = max_num #最大线程
self.cancel = False
self.terminal = False
self.generate_list = [] #当前已经创建的线程
self.free_list = [] #当前空余的线程
def run(self, func, args, callback=None):
"""
线程池执行一个任务
:param func: 任务函数
:param args: 任务函数所需参数
:param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
:return: 如果线程池已经终止,则返回True否则None
"""
if self.cancel:
return
if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
self.generate_thread()
w = (func, args, callback,)
self.q.put(w)
def generate_thread(self):
"""
创建一个线程
"""
t = threading.Thread(target=self.call)
t.start()
def call(self):
"""
循环去获取任务函数并执行任务函数
"""
current_thread = threading.currentThread()
self.generate_list.append(current_thread)
event = self.q.get()
while event != StopEvent:
func, arguments, callback = event
try:
result = func(*arguments)
success = True
except Exception as e:
success = False
result = None
if callback is not None:
try:
callback(success, result)
except Exception as e:
pass
with self.worker_state(self.free_list, current_thread):
if self.terminal:
event = StopEvent
else:
event = self.q.get()
else:
self.generate_list.remove(current_thread)
def close(self):
"""
执行完所有的任务后,所有线程停止
"""
self.cancel = True
full_size = len(self.generate_list)
while full_size:
self.q.put(StopEvent)
full_size -= 1
def terminate(self):
"""
无论是否还有任务,终止线程
"""
self.terminal = True
while self.generate_list:
self.q.put(StopEvent)
self.q.queue.clear()
@contextlib.contextmanager
def worker_state(self, state_list, worker_thread):
"""
用于记录线程中正在等待的线程数
"""
state_list.append(worker_thread)
try:
yield
finally:
state_list.remove(worker_thread)
#如何用高大上版本的线程池
pool = ThreadPool(5)
def callback(status, result):
# status, execute action status
# result, execute action return value
pass
def action(i):
print(i)
for i in range(30):
ret = pool.run(action, (i,), callback)
time.sleep(5)
print(len(pool.generate_list), len(pool.free_list))
print(len(pool.generate_list), len(pool.free_list))
# pool.close()
# pool.terminate()
事件(event)
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False
set:将“Flag”设置为True
#Python2.7
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
def do(event):
print 'start'
event.wait()
print 'execute'
event_obj = threading.Event()
for i in range(10):
t = threading.Thread(target=do, args=(event_obj,))
t.start()
event_obj.clear()
inp = raw_input('input:')
if inp == 'true':
event_obj.set()
Python 进程
from multiprocessing import Process
from multiprocessing import queues
import multiprocessing
def foo(i,arg):
arg.put(i)
print('say hi',i,arg.qsize())
if __name__ == "__main__": #仅作windows平台测试在python,windows平台对Python进程支持不是很好
# li = []
li = queues.Queue(20,ctx=multiprocessing) #通过特殊的queues进行进程共享
for i in range(10):
p = Process(target=foo,args=(i,li,))
#p.daemon = True #主进程运行完后,是否等待子进程
p.start() #进程准备就绪,等待CPU调度
#p.join() #进程排队进行
进程之间的数据共享
主要思路:默认数据不共享(通过公共内存空间)
#方法一,Array (python不常用),但在java和C#里常见。
from multiprocessing import Process,Array #Array 是数组
temp = Array('i', [11,22,33,44])
def Foo(i):
temp[i] = 100+i
for item in temp:
print i,'----->',item
for i in range(2):
p = Process(target=Foo,args=(i,))
p.start()
上面代码的数组数据类型:
'c': ctypes.c_char, 'u': ctypes.c_wchar,
'b': ctypes.c_byte, 'B': ctypes.c_ubyte,
'h': ctypes.c_short, 'H': ctypes.c_ushort,
'i': ctypes.c_int, 'I': ctypes.c_uint,
'l': ctypes.c_long, 'L': ctypes.c_ulong,
'f': ctypes.c_float, 'd': ctypes.c_double
#方法二(方法二里含方法一代码):manage.dict()共享数据 (常用) 特殊的字典
from multiprocessing import Process
from multiprocessing import queues
import multiprocessing
from multiprocessing import Manager
def foo(i,arg):
# arg.put(i)
# print('say hi',i,arg.qsize())
# arg[i] = i + 100
# for item in arg:
# print(item)
# print('================')
arg[i] = i + 100
print(arg.values())
if __name__ == "__main__":
# li = []
# li = queues.Queue(20,ctx=multiprocessing)
obj = Manager()
li = obj.dict()
for i in range(10):
p = Process(target=foo,args=(i,li,))
#p.daemon = True
p.start()
p.join() # 方式二
# 方式一
# import time
# time.sleep(0.1)
进程锁(类似线程锁)
from multiprocessing import Process
from multiprocessing import queues
from multiprocessing import Array
from multiprocessing import RLock, Lock, Event, Condition, Semaphore
import multiprocessing
import time
#RLock, Lock, Event, Condition, Semaphore 方法同线程一样
def foo(i,lis,lc):
lc.acquire()
lis[0] = lis[0] - 1
time.sleep(1)
print('say hi',lis[0])
lc.release()
if __name__ == "__main__":
# li = []
li = Array('i', 1)
li[0] = 10
lock = RLock()
for i in range(10):
p = Process(target=foo,args=(i,li,lock))
p.start()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:apply和apply_async
from multiprocessing import Pool
import time
def f1(arg):
print(arg,'b')
time.sleep(5)
print(arg,'a')
if __name__ == "__main__":
pool = Pool(5) #最大执行5个进程
for i in range(30):
# pool.apply(func=f1,args=(i,)) #串行操作
pool.apply_async(func=f1,args=(i,)) #异步操作
# pool.close() # 所有的任务执行完毕后终止
time.sleep(2)
pool.terminate() # 立即终止
pool.join() #当主进程进行到这里的时候,等待子进程都结束后才能继续走
pass
pool.close()和pool.terminate()区别:close()等所有任务都执行完毕后才终止进程池,terminate()是立即终止进程池,不管现在有没有正在执行的进程
Python协程
协程是人为创造出来的,原理:利用一个线程,分解一个线程为多个微线程
线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
导入库 greenlet gevent
python -m pip install gevent
或者
pip -m install gevent
安装的时候只要安装gevent就可以了,gevent是对greenlet调用,greenlet更底层。
greenlet
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from greenlet import greenlet
def test1():
print 12
gr2.switch()
print 34
gr2.switch()
def test2():
print 56
gr1.switch()
print 78
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() #先执行gr1
gevent
import gevent
def foo():
print('Running in foo')
gevent.sleep(0) #表示进行下一个
print('Explicit context switch to foo again')
def bar():
print('Explicit context to bar')
gevent.sleep(0) ##表示进行下一个
print('Implicit context switch back to bar')
gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),
])
协程代码实例: 一个线程并发3个http请求
from gevent import monkey; monkey.patch_all() #把io请求做了一个封装,要用它才能执行协程
import gevent
import requests
def f(url):
print('GET: %s' % url)
resp = requests.get(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])