一、进程
IO操作(例如从端口、硬盘等读数据)不占用CPU,计算操作占用CPU,在线程中来回切换要占用资源,所以python中的多线程不适合计算操作密集型的任务,适合IO操作密集型的任务,对于计算密集型的任务,可以用多进程来解决(对于多核的CPU,对于一个进程下的线程,即使有多个核,同一时间也只有一个核对一个线程进行操作,但多个核可以同时对多个进程进行操作,可以每个核对一个进程下的线程进行操作)
import multiprocessing
import threading
import time
def thread_run():
print(threading.get_ident()) # 获取线程的线程号
def run_process(name):
time.sleep(1)
print("hello %s!" %name)
t = threading.Thread(target=thread_run)
t.start()
if __name__ == '__main__':
for i in range(10):
p = multiprocessing.Process(target=run_process,args=('process %s' %i,))
p.start()
每一个进程都是由一个父进程启动的
import multiprocessing
import threading
import time
import os
def info(title):
print(title)
print('module name: ',__name__) # 获取模块名
print('parent process: ',os.getppid()) # 获得父进程号,每个进程都是由父进程产生的
print('process id: ',os.getpid()) # 获得本身进程的进程号
def f(name):
info('\033[31;1mchild process function f\033[0m')
print("hello %s!" %name)
if __name__ == '__main__':
info('\033[32;1mmain process\033[0m')
p = multiprocessing.Process(target=f,args=('test_process',))
p.start()
main process
module name: __main__
parent process: 8292
process id: 8348
child process function f
module name: __mp_main__
parent process: 8348
process id: 9108
hello test_process!
进程之间的数据是独立的,数据不能直接被访问,但可以通过下面方式进行进程之间的相互通信
import multiprocessing
def f(obj_q):
obj_q.put([27,'zhushanwei','educated'])
if __name__ == '__main__':
q = multiprocessing.Queue() # 生成一个进程的队列
p = multiprocessing.Process(target=f,args=(q,)) # 子进程的参数为父进程设置的进程队列(必须为进程队列,线程队列queue.Queue不行),
# 这样子进程可以与父进程共享队列中的数据
p.start()
print(q.get())
p.join()
[27, 'zhushanwei', 'educated']
import multiprocessing
def f(conn):
conn.send([27,'zhushanwei','educated']) # 向父进程发送数据
conn.send([25,'fj','educated'])
print("from parent_conn: ",conn.recv()) # 接收父进程发来的数据
if __name__ == '__main__':
parent_conn,child_conn = multiprocessing.Pipe() # 生成一个进程管道,有父进程和子进程两个链接返回值
p = multiprocessing.Process(target=f,args=(child_conn,)) # 把子进程链接作为参数传入进程中
p.start()
print(parent_conn.recv()) # 父进程接收从子进程发来的数据,子进程发送多少次,父进程接收多少次
print(parent_conn.recv())
parent_conn.send("山有木兮木有枝")
p.join()
[27, 'zhushanwei', 'educated']
[25, 'fj', 'educated']
from parent_conn: 山有木兮木有枝
import multiprocessing
import os
def f(dd,ll):
dd[1] = '1'
dd['2'] = 2
dd['zhu'] = 'shanwei'
dd[os.getpid()] = int(os.getpid()) + 1
ll.append(1)
ll.append(os.getpid())
if __name__ == '__main__':
with multiprocessing.Manager() as manager: # 生成一个manager,可用于对数据的修改,等同于manager=multiprocessing.Manager()
d = manager.dict() # 生成一个可以在进程之间进行操作的字典
l = manager.list(range(5)) # 生成一个可以在进程之间进行操作的列表
p_list = [] # 生成一个列表,用于放进程
for i in range(10):
p = multiprocessing.Process(target=f,args=(d,l))
p.start()
p_list.append(p)
for j in p_list:
j.join()
print(d)
print(l)
{'zhu': 'shanwei', 1: '1', 8548: 8549, 6568: 6569, 7836: 7837, 9072: 9073, 7344: 7345, 3048: 3049, 5128: 5129, 7224: 7225, 6808: 6809, '2': 2, 2172: 2173}
[0, 1, 2, 3, 4, 1, 6808, 1, 9072, 1, 6568, 1, 3048, 1, 7836, 1, 7344, 1, 2172, 1, 8548, 1, 7224, 1, 5128]
由于每个进程都需要独立的数据,同一进程过多则会造成资源占用量过大,所以需要进程池来限制进程的数量
import multiprocessing
import os
import time
def foo(i):
time.sleep(2)
print("process: ",os.getpid())
return 'process%s' %i
def bar(arg):
print("--process done-- ",arg,os.getpid())
if __name__ == '__main__':
print(os.getpid())
pool_test = multiprocessing.Pool(3) # 定义一个进程池,参数为允许放入进程池中的进程的最多个数
for i in range(5):
# pool_test.apply(func=foo,args=(i,)) # 把进程放入进程池中,有两个方法apply和apply_async,apply是进程串行,apply_async是进程并行
pool_test.apply_async(func=foo,args=(i,),callback=bar) # callback为回调,即执行完前边的func=foo,父进程再调用执行callbake=bar,
# 其中bar中的参数为foo函数中的返回值
print('end')
pool_test.close() # 关闭进程池
pool_test.join() # 必须先关闭再join,并且join这行代码不能省略,如果注释则程序会直接关闭
7696
end
process: 7944
--process done-- process0 7696
process: 4180
--process done-- process1 7696
process: 8636
--process done-- process2 7696
process: 7944
--process done-- process3 7696
process: 4180
--process done-- process4 7696
协程,又称微线程,是一种用户态的轻量级线程
所以若程序遇到阻塞操作,例如IO操作,协程就切换控制
import greenlet
import gevent
# 手动切换协程
def test1():
print(12)
gre2.switch()
print(34)
gre2.switch()
def test2():
print(56)
gre1.switch()
print(78)
gre1 = greenlet.greenlet(test1) # 生成一个协程
gre2 = greenlet.greenlet(test2)
gre1.switch() # 切换/启动gre1这个协程
# 自动切换协程
def f1():
print("f1 running...")
gevent.sleep(3)
print("go on f1 running...")
def f2():
print("f2 running...")
gevent.sleep(2)
print("go on f2 running...")
def f3():
print("f3 running...")
gevent.sleep(1)
print("go on f3 running...")
gevent.joinall([gevent.spawn(f1), # 生成协程
gevent.spawn(f2),
gevent.spawn(f3)])
12
56
34
78
f1 running...
f2 running...
f3 running...
go on f3 running...
go on f2 running...
go on f1 running...
通过协程,实现多并发爬取网页
import greenlet
import gevent
import time
from gevent import monkey
from urllib import request
monkey.patch_all() # 把当前所有的IO操作做上标记,这样gevent才能识别程序中的IO操作,才能串行执行
def scrip_url(url):
print("GET: %s" %url)
respon = request.urlopen(url) # 打开网页
data = respon.read() # 读取网页的数据
# f = open("url_data.html",'wb')
# f.write(data) # 保存数据
# f.close()
print("%s bytes received from %s" %(len(data),url))
# 串行执行
url_list = ['https://www.python.org/','https://www.yahoo.com/','https://github.com/']
c_time = time.time()
for url_t in url_list:
scrip_url(url_t)
print("串行时间为:%s" %(time.time()-c_time))
# 异步执行
a_time = time.time()
gevent.joinall([gevent.spawn(scrip_url,'https://www.python.org/'),
gevent.spawn(scrip_url,'https://www.yahoo.com/'),
gevent.spawn(scrip_url,'https://github.com/')])
print("并行时间为:%s" %(time.time()-a_time))
GET: https://www.python.org/
48725 bytes received from https://www.python.org/
GET: https://www.yahoo.com/
480761 bytes received from https://www.yahoo.com/
GET: https://github.com/
55394 bytes received from https://github.com/
串行时间为:6.311866521835327
GET: https://www.python.org/
GET: https://www.yahoo.com/
GET: https://github.com/
48725 bytes received from https://www.python.org/
55394 bytes received from https://github.com/
479781 bytes received from https://www.yahoo.com/
并行时间为:2.3351285457611084
通过协程,实现多并发的socket
import gevent
from gevent import monkey
import socket
monkey.patch_all() # 把当前所有的IO操作做上标记,这样gevent才能识别程序中的IO操作,才能串行执行
# server服务器端
def server(port):
s = socket.socket()
s.bind(('0.0.0.0',port))
s.listen(50)
while True:
cli,addr = s.accept()
gevent.spawn(handle_conn,cli) # 把handle方法和链接实例对象,传入协程
def handle_conn(conn):
try:
while True:
data = conn.recv(1024)
print("data: ",data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR) # 没有数据,关闭连接
except Exception as error:
print(error)
finally:
conn.close()
if __name__ == '__main__':
server(80001)
# 客户端
host,port = 'localhost',8001
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ss.connect((host,port))
while True:
msg = input(">>: ",encoding='utf-8')
ss.send(msg)
data = ss.recv(1024)
print("received: ",data)
ss.close()
二、事件驱动和异步IO
在一个线程下,物种模式的IO操作的阻塞情况如下
对于多路复用的IO来说,有select、poll、epoll三种方式
用多路复用的IO模拟服务器端的socket多并发,以select为例
import select
import queue
import socket
# 服务器端
server_test = socket.socket()
server_test.bind(("localhost",9999))
server_test.listen(1000)
server_test.setblocking(False) # 只有设置在IO操作不阻塞,才能用IO多路复用
inputs = [server_test] # 存放要监听的链接,初始化时为server端本身的链接
outputs = [] # 存放有监听到数据返回的链接
msg_dic = {} # 字典用于存放服务器端给客户端连接发送的数据,每个键对应一个链接
while True:
readable,writeable,exceptional = select.select(inputs,outputs,inputs) # 利用select来同时监听多个客户端发来的链接,第一个参数为需要监听的链接,
# 第二参数为有数据返回的链接,第三个参数为监听可能有错误的链接,
# 第一参数和第三参数为同一个列表,返回值为三个列表,第一个列表存放监听到的链接,
# 第二个列表存放有数据返回的链接,第三个列表存放出现异常(例如断开)的链接
print(readable,writeable,exceptional)
for r in readable: # 循环查看监听到的链接,查看哪个链接是活动的
if r is server_test: # 如果活动的server_test,证明客户端有新链接接入
conn,addr = r.accept()
print("有一个新的链接:",addr)
inputs.append(conn) # 把这个新链接放入到要监听的列表中
msg_dic[conn] = queue.Queue() # 初始化字典,每有一个客户端连接,就把链接放入字典,同时生成这个链接的一个队列,
# 队列用于存放服务器端要发给这个链接的数据
else:
data_test = r.recv(1024) # 如果活动的不是server_test,证明是客户端的链接,有数据传入,接收数据
print("接收数据:",data_test)
# r.send(data_test) # 把数据发送给客户端
# 也可以把数据放入链接对应的队列中
msg_dic[r].put(data_test) # 把数据放入链接对应列表
outputs.append(r) # 把有数据返回的活动链接放入到outputs列表中,利用select进行监听,下一次循环放入到writeable列表中
for w in writeable: # 循环查看writeable列表,是否有要返回数据的链接
data_to_client = msg_dic[w].get() # 从链接对应的队列中取回数据
w.send(data_to_client) # 给对应的客户端连接发送数据
outputs.remove(w) # 发送完毕从outputs列表中移除这个链接,避免下一次循环时重复监听、发送数据
for e in exceptional: # 循环exceptional列表,查看是否有错误链接存在
if e in outputs:
outputs.remove(e) # 如果错误链接存在于outputs列表中,则移除
inputs.remove(e) # 所有链接都在inputs列表中,直接移除,不用判断
del msg_dic[e] # 从字典中删除这个链接,及其对应的队列
import socket
# 客户端
host_test = "localhost"
port_test = 9999
s_test = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s_test.connect((host_test,port_test))
while True:
msg_test = bytes(input(">>: "),encoding='utf-8')
s_test.send(msg_test)
data_test = s_test.recv(1024)
print(data_test)
python中有封装好的关于多路复用IO的模块selector,模块默认用epoll方法,若不支持epoll(例如,windows不支持epoll),者使用select方法
import selectors
import socket
# 服务器端
def accept(sock,mask):
conn,addr = sock.accept()
print("conn: ",conn," addr: ",addr)
conn.setblocking(False)
sel.register(conn,selectors.EVENT_READ,read) # 向新连接中注册read回调函数
def read(conn,mask):
data = conn.recv(1024)
if data:
print("data: ",data, "to: ",conn)
conn.send(data)
else:
print("closing: ",conn)
sel.unregister(conn) # 取消注册
conn.close()
sel = selectors.DefaultSelector() # 设置一个select的实例对象
sock = socket.socket()
sock.bind(("localhost",9999))
sock.listen(1000)
sock.setblocking(False)
sel.register(sock,selectors.EVENT_READ,accept) # 向对象中注册accept回调函数
while True:
events = sel.select() # 默认阻塞,有活动的链接就返回后动的链接列表
for key,mask in events:
callback = key.data # 相当于服务器端的accept
callback(key.fileobj, mask) # key.fileobj相当于文件句柄,socket.socket()
import socket
import sys
# 客户端
host_test = "localhost"
port_test = 9999
# socks = [socket.socket(socket.AF_INET,socket.SOCK_STREAM),
# socket.socket(socket.AF_INET,socket.SOCK_STREAM),
# socket.socket(socket.AF_INET,socket.SOCK_STREAM),
# socket.socket(socket.AF_INET,socket.SOCK_STREAM),
# socket.socket(socket.AF_INET,socket.SOCK_STREAM),]
socks = [socket.socket(socket.AF_INET,socket.SOCK_STREAM) for i in range(400)]
msg = [b'zhushanwei',
b'is',
b'handsome']
for s in socks:
s.connect((host_test,port_test))
for message in msg:
for s in socks:
s.send(message)
data = s.recv(1024)
print("%s received %s" %(s.getsockname(),data))
if not data:
print(sys.stderr,'close socket: ',s.getsockname())
三、RabbitMQ消息队列
线程队列只能在一个进程之内的线程之间进行通信,进程队列可以父进程和子进程、同一个父进程下的子进程之间进行通信,而RabbitMQ可以在不同进程之间进行通信,一个进程看作生产者,另一个进程看作消费者,若有多个消费者,生产者发送的数据会被消费者逐个接收,每次有一个消费者接收数据,当消费者接收到数据,调用callback函数进行数据的处理,当一个消费者处理数据中途停止时,如果参数no_ack=False,或者不写(默认为False),则会把数据自动转到下一个消费者,若消费者处理数据过程中止则进行转移到下一个,直到所有消费者都停止,生成者把数据保留到队列中,当消费者重新启动时,生产者把数据继续传给新的消费者进行处理,也可以在回调函数中加入ch.basic_ack(delivery_tag=method.delivery_tag),来手动确认数据是否处理完,对于不同的硬件配置,处理的速度不同,若对消费者进行平均的数据发送,在同一时间内有的会处理不完,所以需要按处理量和速度来对消费者发送数据,rabbitMQ是对消费者队列进行检测,若队列中还有数据没有处理,就不再发送数据
import pika
# 生产者
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost')) # 相当于建立一个socket
channel = connection.channel() # 声明一个管道,相当于开辟一条路
channel.queue_declare(queue='task_q',durable=True) # 声明一个队列,并给队列命名,参数durable=True,保证生产者端停止重启后原来的队列还在
channel.basic_publish(exchange='',
routing_key='task_q', # 队列名称
body='hello world!', # 发送消息的内容
properties=pika.BasicProperties(delivery_mode=2) # 保证生成者端消息持久化,当停止重启服务时,原来发送的数据还存在,消费者还能接收处理这些数据
)
print("[x] send 'hello world!'")
connection.close()
import pika
import time
# 消费者
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue='task_q',durable=True) # 声明从哪个管道收消息
def callback(ch,method,properties,body):
print(ch,method,properties)
time.sleep(30)
print("[x] received %s" %body)
ch.basic_ack(delivery_tag=method.delivery_tag) # 手动确认数据是否处理完
channel.basic_qos(prefetch_count=1) # 队列缓存中最多有1条数据
channel.basic_consume(callback,queue='task_q',no_ack=False) # 接收消息,如果收到消息就调用callback函数来处理消息,no_ack为不确认数据是否处理完毕
print("[*] waiting for message. To exit press Ctrl+C")
channel.start_consuming() # 接收完发送的数据之后,会一直等待,下一次数据发送会再次接收
[x] send 'hello world!'
[*] waiting for message. To exit press Ctrl+C
<pika.adapters.blocking_connection.BlockingChannel object at 0x000000D9C40D2A58> <Basic.Deliver(['consumer_tag=ctag1.557dec1cdc8b4f90ac0a89193d3ab6d2', 'delivery_tag=1', 'exchange=', 'redelivered=False', 'routing_key=test_q'])> <BasicProperties>
[x] received b'hello world!'
以上是一对一的发送,即生产者发送一条数据,只有一个消费者接受,若要多个消费者接收需要用到exchange
fanout类型广播模型,具有实时性,生产者发送数据,同一时间消费者若没有接收,则以后也接收不到此时生产者发送的数据,只能接收同一时刻生产者发送的数据
import pika
# 生产者
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs', # 给exchange命名
type='fanout' # 定义exchange的类型
) # 声明一个广播
msg = "hello world!"
channel.basic_publish(exchange='logs',
routing_key='', # 广播fanout模式不需要通道
body=msg
)
print("[x] send message: %s" %msg)
connection.close()
import pika
# 消费者
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',
type='fanout')
result = channel.queue_declare(exclusive=True) # 生成queue的对象,exclusive为唯一性,不指定queue的名称,会随机分配一个名字,exclusive=True会使使用此queue的消费者断开后,自动删除queue
queue_name = result.method.queue # 用queue的对象,生成一个队列的名称
print("random queue name: ",queue_name)
channel.queue_bind(exchange='logs',queue=queue_name) # 将随机生成的queue绑定到要接收数据的exchange上
print("[*] waiting for logs. To exit press Ctrl+C")
def callback(ch,method,properties,body):
print("[x] received %s" %body)
channel.basic_consume(callback,queue=queue_name,no_ack=True)
print("[*] waiting for message. To exit press Ctrl+C")
channel.start_consuming() # 接收完发送的数据之后,会一直等待,下一次数据发送会再次接收
服务器端向客户端发送数据,数据流是单向的,如何把客户端执行的结果再反向对服务器端发送,变为双向流的数据传输,利用remote procedure call,RPC操作,让客户端既是生产者又是消费者
import pika
# 生产者
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='rpc_queue')
def fib(n):
if n==0:
return 0
elif n==1:
return 1
else:
return fib(n-1)+fib(n-2)
def on_request(ch,method,props,body):
n = int(body)
print("[*] fib(%s)" %n)
response = fib(n)
ch.basic_publish(exchange='',
routing_key=props.reply_to,
properties=pika.BasicProperties(correlation_id=props.correlation_id),
body=str(response))
ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_qos(prefetch_count=1)
channel.basic_consume(on_request,queue='rpc_queue') # 通过接收命令,调用回调函数on_request发送数据(发送通道为props.reply_to),
# 然后通过rpc_queue接收客户端发送的数据
print("[x] waiting RPC requests")
channel.start_consuming()
import pika
import uuid
# 消费者
class FibnacciRpcClient(object):
def __init__(self): # 实例化对象先接收数据
self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
self.channel = self.connection.channel()
result = self.channel.queue_declare(exclusive=True)
self.callback_queue = result.method.queue
self.channel.basic_consume(self.on_response,
no_ack=True,
queue=self.callback_queue)
def on_response(self,ch,method,props,body):
if self.corr_id == props.correlation_id:
self.response = body
def call(self,n): # 实例化对象调用这个函数来发送数据
self.response = None
self.corr_id = str(uuid.uuid4())
self.channel.basic_publish(exchange='',
routing_key='rpc_queue',
properties=pika.BasicProperties(
reply_to=self.callback_queue,
correlation_id=self.corr_id
),
body=str(n))
while self.response is None:
self.connection.process_data_events() # 非阻塞版start_consuming()
return int(self.response)
fibonacci_rpc = FibnacciRpcClient()
print("[x] requesting fib(30)")
response = fibonacci_rpc.call(30)
print("[*] got %s" %response)
direct类型广播模型
import pika
import sys
# 生产者
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs', # 给exchange命名
type='direct' # 定义exchange的类型
) # 声明一个广播
severity = sys.argv[1] if len(sys.argv[1])>1 else 'info'
msg = ' '.join(sys.argv[2:]) or "hello world!"
channel.basic_publish(exchange='direct_logs',
routing_key=severity, # 广播direct模式的routing_key为自己设定的重要程度
body=msg
)
print("[x] send message: %s" %msg)
connection.close()
import pika
import sys
# 消费者
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',
type='direct')
result = channel.queue_declare(exclusive=True) # 生成queue的对象,exclusive为唯一性,不指定queue的名称,会随机分配一个名字,exclusive=True会使使用此queue的消费者断开后,自动删除queue
queue_name = result.method.queue # 用queue的对象,生成一个队列的名称
print("random queue name: ",queue_name)
severities = sys.argv[1:]
if not severities:
sys.stderr.write("user: %s [info] [warnning] [error] \n" %sys.argv[0])
sys.exit(1)
for severity in severities:
channel.queue_bind(exchange='direct_logs',queue=queue_name,routing_key=severity) # 将随机生成的queue绑定到要接收数据的exchange上
print("[*] waiting for logs. To exit press Ctrl+C")
def callback(ch,method,properties,body):
print("[x] received %s" %body)
channel.basic_consume(callback,queue=queue_name,no_ack=True)
print("[*] waiting for message. To exit press Ctrl+C")
channel.start_consuming() # 接收完发送的数据之后,会一直等待,下一次数据发送会再次接收
topic类型广播
import pika
import sys
# 生产者
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs', # 给exchange命名
type='topic' # 定义exchange的类型
) # 声明一个广播
rout_key = sys.argv[1] if len(sys.argv[1])>1 else 'anonymous.info'
msg = ' '.join(sys.argv[2:]) or "hello world!"
channel.basic_publish(exchange='topic_logs',
routing_key=rout_key, # 广播direct模式的routing_key为自己设定的重要程度
body=msg
)
print("[x] send message: %s" %msg)
connection.close()
import pika
import sys
# 消费者
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs',
type='topic')
result = channel.queue_declare(exclusive=True) # 生成queue的对象,exclusive为唯一性,不指定queue的名称,会随机分配一个名字,exclusive=True会使使用此queue的消费者断开后,自动删除queue
queue_name = result.method.queue # 用queue的对象,生成一个队列的名称
print("random queue name: ",queue_name)
bind_keys = sys.argv[1:]
if not bind_keys:
sys.stderr.write("user: %s [bind_key]...\n" %sys.argv[0])
sys.exit(1)
for bind_key in bind_keys:
channel.queue_bind(exchange='topic_logs',queue=queue_name,routing_key=bind_key) # 将随机生成的queue绑定到要接收数据的exchange上
print("[*] waiting for logs. To exit press Ctrl+C")
def callback(ch,method,properties,body):
print("[x] received %s" %body)
channel.basic_consume(callback,queue=queue_name,no_ack=True)
print("[*] waiting for message. To exit press Ctrl+C")
channel.start_consuming() # 接收完发送的数据之后,会一直等待,下一次数据发送会再次接收
生产者端
消费者端1
消费者端2
四、redis
import redis
'''
# redis链接
r = redis.Redis(host='localhost',port=6379)
r.set('foo','bar')
print(r.get('foo'))
'''
# 为了避免每次建立连接、断开的开销,需要建立一个连接池
pool = redis.ConnectionPool(host='localhost',port=6379)
r = redis.Redis(connection_pool=pool)
r.set('foo','bar')
print(r.get('foo'))
在CMD命令窗口下运行 redis-server.exe redis.conf 启动redis服务的doc窗口,不用关闭,因为服务需要一直执行,关闭服务,直接关闭窗口就行
redis对于string的操作
就是把原来key的value替换为新的值,并返回原来的值,name要存在
即功能为对key的value进行切片操作
即对key的value进行替换,从value的索引位置offset开始,用新的value依次替换字符,若新的value太长则向后添加
即把key的value字符按照ASCII码转为二进制依次排列,在按照位的索引改变value,然后再转为字符
即key不存在时,对应的value设为1,若存在则其value加1自增
即在key对应的value后面追加字符串,功能相当于字符串合并
redis的hash操作,hash的内存中的存储格式如下
其中匹配规则可以进行模糊匹配,例如 match = *k、k*、*k*
redis的列表的操作
参数 num 在 value 的前面
redis的集合set操作
向name中添加元素,没有name则新建
获取集合元素之间的差集,keys为集合的名称
将keys之间的差集放入dest这个集合中
获取集合元素的交集
将keys之间的交集放入dest这个集合中
从name中找出符合条件match的值,用法类似于hscan
自增 name 中元素 value 对应的分数
只要有相同的value就进行aggregate操作
只要有相同的value就进行aggregate操作
从name中找出符合条件match的值,用法类似于hscan
redis其他常用操作
import redis
import time
# 为了避免每次建立连接、断开的开销,需要建立一个连接池
pool = redis.ConnectionPool(host='localhost',port=6379)
r = redis.Redis(connection_pool=pool)
pipe = r.pipeline(transaction=True)
pipe.set('sex','man')
time.sleep(10)
pipe.set('name','zhushanwei')
time.sleep(10)
pipe.set('hobby','game')
pipe.execute() # 利用管道一次执行
import redis
# 发布者
class RedisHelper(object):
def __init__(self):
self.__connection = redis.Redis(host='localhost')
self.chan_sub = 'fm104.5' # 接收频道
self.chan_pub = 'fm104.5' # 发布频道
def public(self,msg): # 发布函数
self.__connection.publish(self.chan_pub,msg)
return True
def subscribe(self): # 订阅函数
sub = self.__connection.pubsub() # 开始订阅,相当于打开收音机
sub.subscribe(self.chan_sub) # 调节频道
sub.parse_response() # 准备接受,再次调用parse_response()才能接收
return sub
obj = RedisHelper()
obj.public('hello world!')
import redis
# 订阅者
class RedisHelper(object):
def __init__(self):
self.__connection = redis.Redis(host='localhost')
self.chan_sub = 'fm104.5' # 接收频道
self.chan_pub = 'fm104.5' # 发布频道
def public(self,msg): # 发布函数
self.__connection.publish(self.chan_pub,msg)
return True
def subscribe(self): # 订阅函数
sub = self.__connection.pubsub() # 开始订阅,相当于打开收音机
sub.subscribe(self.chan_sub) # 调节频道
sub.parse_response() # 准备接受,再次调用parse_response()才能接收
return sub # sub包含message标识、频道、信息
obj = RedisHelper()
redis_sub = obj.subscribe() # 订阅
while True:
meg = redis_sub.parse_response() # 再次调用parse_response()接收
print(meg[2]) # meg为[b'message', b'fm104.5', b'hello world!']
b'hello world!'