Python进程间通信 multiProcessing Queue队列实现详解

一、进程间通信

IPC(Inter-Process Communication)

IPC机制:实现进程之间通讯

管道:pipe 基于共享的内存空间

队列:pipe+锁的概念--->queue

二、队列(Queue)

2.1 概念-----multiProcess.Queue

创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。

Queue([maxsize])创建共享的进程队列。

参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。

底层队列使用管道和锁定实现。

2.2 Queue方法使用

2.2.1 q.get的使用:

是从队列里面取值并且把队列面的取出来的值删掉,没有参数的情况下就是是默认一直等着取值

就算是队列里面没有可取的值的时候,程序也不会结束,就会卡在哪里,一直等着

from multiprocessing import Queue
q = Queue() # 生成一个队列对象
# put方法是往队列里面放值
q.put('Cecilia陈')
q.put('xuchen')
q.put('喜陈')

# get方法是从队列里面取值
print(q.get())
print(q.get())
print(q.get())

q.put(5)
q.put(6)
print(q.get())

Cecilia陈

xuchen

喜陈

5

2.2.2 Queue(参数) +参数的使用:

Queue加参数以后,参数是数值

参数实几就表示实例化的这个Queue队列可以放几个值

当队列已经满的时候,再放值,程序会阻塞,但不会结束

from multiprocessing import Queue
q = Queue(3)
q.put('Cecilia陈')
q.put('xuchen')
q.put('喜陈')
print(q.full()) # 判断队列是否满了 返回的是True/False
q.put(2) # 当队列已经满的时候,再放值,程序会阻塞,但不会结束

True 队列已经满了

2.2.3 q.put(参数1,参数2,参数3,参数4):

q.put(self, obj, block=True, timeout=None)

self :put就相当于是Queue里的一个方法,这个时候q.put就相当于是队列对象q来调用对象的绑定方法,这个参数可以省略即可

obj:是我们需要往队列里面放的值

block=True :队列如果满了的话,再往队列里放值的话会等待,程序不会结束

timeout=None:是再block这个参数的基础上的,当block的值为真的时候,timeout是用来等待多少秒,如果再这个时间里,队列一直是满的,那么程序就会报错并结束(Queue.Full异常)

from multiprocessing import Queue
q = Queue(3)
q.put('zhao',block=True,timeout=2)
q.put('zhao',block=True,timeout=2)
q.put('zhao',block=True,timeout=2)
q.put('zhao',block=True,timeout=5) # 此时程序将对等待5秒以后报错了

2.2.4 q.get(参数1,参数2,参数3,参数4):

q.get(self,block=True, timeout=None)

self :get就相当于是Queue里的一个方法,这个时候q.get就相当于是队列对象q来调用对象的绑定方法,这个参数可以省略即可

block=True :从队列q对象里面取值,如果娶不到值的话,程序不会结束

timeout=None:是再block这个参数的基础上的,当block的值为真的时候,timeout是用来等待多少秒,如果再这个时间里,get取不到队列里面的值的话,那么程序就会报错并结束(queue.Empty异常)

from multiprocessing import Queue
q = Queue()
q.put('Cecilia陈')
print(q.get())
q.get(block=True,timeout=2) # 此时程序会等待2秒后,报错了,队列里面没有值了

2.2.5 block=False:

如果block的值是False的话,那么put方法再队列是满的情况下,不会等待阻塞,程序直接报错(Queue.Full异常)结束

如果block的值是False的话,那么get方法再队列里面没有值的情况下,再去取的时候,不会等待阻塞,程序直接报错(queue.Empty异常)结束

1.put()的block=False

from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.full())
q.put('xichen',block=False) # 队列已经满了,我不等待了,直接报错

2.get()的block=Flase

from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.get())
print(q.get())
print(q.get(block=False)) # 队列已经没有值了,我不等待了,直接报错

2.2.6 put_nowait()/get_nowait()

1.put_nowait() 相当于bolok=False,队列满的时候,再放值的时候,程序不等待,不阻塞,直接报错

from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.full())

q.put_nowait('xichen') # 程序不等待,不阻塞,直接报错

2.get_nowait() 相当于bolok=False,当队列里没有值的时候,再取值的时候,程序不等待,不阻塞,程序直接报错

from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.get())
print(q.get())
print(q.full())
q.get_nowait()# 再取值的时候,程序不等待,不阻塞,程序直接报错

三、代码实例

3.1 单看队列的存取数据用法

这个例子还没有加入进程通信,只是先来看看队列为我们提供的方法,以及这些方法的使用和现象。

'''
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''

from multiprocessing import Queue
q=Queue(3)

#put ,get ,put_nowait,get_nowait,full,empty
q.put(3)
q.put(3)
q.put(3)
# q.put(3)  # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。
      # 如果队列中的数据一直不被取走,程序就会永远停在这里。
try:
  q.put_nowait(3) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。
except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。
  print('队列已经满了')

# 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。
print(q.full()) #满了
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。
try:
  q.get_nowait(3) # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。
except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。
  print('队列已经空了')

print(q.empty()) #空了

3.2 子进程向父进程发送数据

这是一个queue的简单应用,使用队列q对象调用get函数来取得队列中最先进入的数据。

from multiprocessing import Process, Queue
def f(q,name,age):
  q.put(name,age) #调用主函数中p进程传递过来的进程参数 put函数为向队列中添加一条数据。
if __name__ == '__main__':
  q = Queue() #创建一个Queue对象
  p = Process(target=f, args=(q,'Cecilia陈',18)) #创建一个进程
  p.start()
  print(q.get())
  p.join()

['Cecilia陈', 18]

四、生产者消费者模型

生产者: 生产数据的任务

消费者: 处理数据的任务

生产者--队列(盆)-->消费者

生产者可以不停的生产,达到了自己最大的生产效率,消费者可以不停的消费,也达到了自己最大的消费效率.

生产者消费者模型大大提高了生产者生产的效率和消费者消费的效率.

补充: queue不适合传大文件,通产传一些消息.

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

4.1 为什么要使用生产者和消费者模型

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

4.2 什么是生产者消费者模型

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

4.3 基于Queue队列实现的生产者消费者模型

from multiprocessing import Queue,Process
# 生产者
def producer(q,name,food):
  for i in range(3):
    print(f'{name}生产了{food}{i}')
    res = f'{food}{i}'
    q.put(res)
# 消费者
def consumer(q,name):
  while True:
    res = q.get(timeout=5)
    print(f'{name}吃了{res}')
if __name__ == '__main__':
  q = Queue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
  p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
  c1 = Process(target=consumer,args=(q,'Tom'))
  p1.start()
  c1.start()

此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。

解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。

4.4 改良版----生产者消费者模型

注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号

from multiprocessing import Queue,Process
def producer(q,name,food):
  for i in range(3):
    print(f'{name}生产了{food}{i}')
    res = f'{food}{i}'
    q.put(res)
  q.put(None) # 当生产者结束生产的的时候,我们再队列的最后再做一个表示,告诉消费者,生产者已经不生产了,让消费者不要再去队列里拿东西了
def consumer(q,name):
  while True:
    res = q.get(timeout=5)
    if res == None:break # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
    print(f'{name}吃了{res}')
if __name__ == '__main__':
  q = Queue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
  p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
  c1 = Process(target=consumer,args=(q,'Tom'))
  p1.start()
  c1.start()

4.5 主进程在生产者生产结束以后,发送结束信号

使用这个方法的话,是很low的,有几个消费者就要在主进程中向队列中put几个结束信号

from multiprocessing import Queue,Process
import time,random

def producer(q,name,food):
  for i in range(3):
    print(f'{name}生产了{food}{i}')
    time.sleep((random.randint(1,3)))
    res = f'{food}{i}'
    q.put(res)
  # q.put(None) # 当生产者结束生产的的时候,我们再队列的最后再做一个表示,告诉消费者,生产者已经不生产了,让消费者不要再去队列里拿东西了

def consumer(q,name):
  while True:
    res = q.get(timeout=5)
    if res == None:break # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
    time.sleep((random.randint(1, 3)))
    print(f'{name}吃了{res}')

if __name__ == '__main__':
  q = Queue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
  # 多个生产者进程
  p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
  p2 = Process(target=producer,args=(q,'xichen','冰激凌'))
  p3 = Process(target=producer,args=(q,'喜陈','可乐'))
  # 多个消费者进程
  c1 = Process(target=consumer,args=(q,'Tom'))
  c2 = Process(target=consumer,args=(q,'jack'))

  # 告诉操作系统启动生产者进程
  p1.start()
  p2.start()
  p3.start()

  # 告诉操作系统启动消费者进程
  c1.start()
  c2.start()

  p1.join()
  p2.join()
  p3.join()

  q.put(None) # 几个消费者put几次
  q.put(None)

五、JoinableQueue方法

创建可连接的共享进程队列。这就像是一个Queue对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

5.1 方法介绍

JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:

q.task_done():使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。

q.join():生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。

5.2 joinableQueue队列实现生产者消费者模型

from multiprocessing import Queue,Process,JoinableQueue
import time,random

def producer(q,name,food):
  for i in range(3):
    print(f'{name}生产了{food}{i}')
    # time.sleep((random.randint(1,3)))
    res = f'{food}{i}'
    q.put(res)
  # q.put(None) # 当生产者结束生产的的时候,我们再队列的最后再做一个表示,告诉消费者,生产者已经不生产了,让消费者不要再去队列里拿东西了
  q.join()

def consumer(q,name):
  while True:
    res = q.get(timeout=5)
    # if res == None:break # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
    # time.sleep((random.randint(1, 3)))
    print(f'{name}吃了{res}')
    q.task_done()#向q.join()发送一次信号,证明一个数据已经被取走了

if __name__ == '__main__':
  q = JoinableQueue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
  # 多个生产者进程
  p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
  p2 = Process(target=producer,args=(q,'xichen','冰激凌'))
  p3 = Process(target=producer,args=(q,'喜陈','可乐'))
  # 多个消费者进程
  c1 = Process(target=consumer,args=(q,'Tom'))
  c2 = Process(target=consumer,args=(q,'jack'))

  # 告诉操作系统启动生产者进程
  p1.start()
  p2.start()
  p3.start()

  # 把生产者设为守护进程
  c1.daemon = True
  c2.daemon = True
  # 告诉操作系统启动消费者进程
  c1.start()
  c2.start()

  p1.join()
  p2.join()
  p3.join() # 等待生产者生产完毕

  print('主进程')

  ### 分析
  # 生产者生产完毕--这是主进程最后一行代码结束--q.join()消费者已经取干净了,没有存在的意义了
  # 这是主进程最后一行代码结束,消费者已经取干净了,没有存在的意义了.守护进程的概念.

5.3 测试joinableQueue

from multiprocessing import Process,Queue,JoinableQueue
q = JoinableQueue()
q.put('zhao') # 放队列里一个任务
q.put('qian')
print(q.get())
q.task_done() # 完成了一次任务
print(q.get())
q.task_done() # 完成了一次任务
q.join() #计数器不为0的时候 阻塞等待计数器为0后通过

# 想象成一个计数器 :put +1  task_done -1

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Python多进程multiprocessing.Pool类详解

    multiprocessing模块 multiprocessing包是Python中的多进程管理包.它与 threading.Thread类似,可以利用multiprocessing.Process对象来创建一个进程.该进程可以允许放在Python程序内部编写的函数中.该Process对象与Thread对象的用法相同,拥有is_alive().join([timeout]).run().start().terminate()等方法.属性有:authkey.daemon(要通过start()设置)

  • Python Multiprocessing多进程 使用tqdm显示进度条的实现

    1.背景 在python运行一些,计算复杂度比较高的函数时,服务器端单核CPU的情况比较耗时,因此需要多CPU使用多进程加快速度 2.函数要求 笔者使用的是:pathos.multiprocessing 库,进度条显示用tqdm库,安装方法: pip install pathos 安装完成后 from pathos.multiprocessing import ProcessingPool as Pool from tqdm import tqdm 这边使用pathos的原因是因为,multip

  • 利用Python实现在同一网络中的本地文件共享方法

    本文利用Python3启动简单的HTTP服务器,以实现在同一网络中共享本地文件. 启动HTTP服务器 打开终端,转入目标文件所在文件夹,键入以下命令: $ cd /Users/zero/Documents/localFiles # python -m http.server <port number> $ sudo python3 -m http.server 8092 Serving HTTP on 0.0.0.0 port 8092 (http://0.0.0.0:8092/) ... 生

  • Python3多进程 multiprocessing 模块实例详解

    本文实例讲述了Python3多进程 multiprocessing 模块.分享给大家供大家参考,具体如下: 多进程 Multiprocessing 模块 multiprocessing 模块官方说明文档 Process 类 Process 类用来描述一个进程对象.创建子进程的时候,只需要传入一个执行函数和函数的参数即可完成 Process 示例的创建. star() 方法启动进程, join() 方法实现进程间的同步,等待所有进程退出. close() 用来阻止多余的进程涌入进程池 Pool 造

  • Python多进程库multiprocessing中进程池Pool类的使用详解

    问题起因 最近要将一个文本分割成好几个topic,每个topic设计一个regressor,各regressor是相互独立的,最后汇总所有topic的regressor得到总得预测结果.没错!类似bagging ensemble!只是我没有抽样.文本不大,大概3000行,topic个数为8,于是我写了一个串行的程序,一个topic算完之后再算另一个topic.可是我在每个topic中用了GridSearchCV来调参,又要选特征又要调整regressor的参数,导致参数组合一共有1782种.我真

  • Python multiprocessing.Manager介绍和实例(进程间共享数据)

    Python中进程间共享数据,处理基本的queue,pipe和value+array外,还提供了更高层次的封装.使用multiprocessing.Manager可以简单地使用这些高级接口. Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问.从而达到多进程间数据通信且安全. Manager支持的类型有list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaph

  • python 进程间数据共享multiProcess.Manger实现解析

    一.进程之间的数据共享 展望未来,基于消息传递的并发编程是大势所趋 即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据. 这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中. 但进程间应该尽量避免通信,即便需要通信,也应该选择进程安全的工具来避免加锁带来的问题. 以后我们会尝试使用数据库来解决现在进程之间的数据共享问题. 1.1 Manager模块介绍 虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于

  • Python多进程并发(multiprocessing)用法实例详解

    本文实例讲述了Python多进程并发(multiprocessing)用法.分享给大家供大家参考.具体分析如下: 由于Python设计的限制(我说的是咱们常用的CPython).最多只能用满1个CPU核心. Python提供了非常好用的多进程包multiprocessing,你只需要定义一个函数,Python会替你完成其他所有事情.借助这个包,可以轻松完成从单进程到并发执行的转换. 1.新建单一进程 如果我们新建少量进程,可以如下: import multiprocessing import t

  • Python进程间通信 multiProcessing Queue队列实现详解

    一.进程间通信 IPC(Inter-Process Communication) IPC机制:实现进程之间通讯 管道:pipe 基于共享的内存空间 队列:pipe+锁的概念--->queue 二.队列(Queue) 2.1 概念-----multiProcess.Queue 创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递. Queue([maxsize])创建共享的进程队列. 参数 :maxsize是队列中允许的最大项数.如果省略此参数,则无大小限制

  • Python collections.deque双边队列原理详解

    队列是一种只允许在一端进行插入操作,而在另一端进行删除操作的线性表. 在Python文档中搜索队列(queue)会发现,Python标准库中包含了四种队列,分别是queue.Queue / asyncio.Queue / multiprocessing.Queue / collections.deque. collections.deque deque是双端队列(double-ended queue)的缩写,由于两端都能编辑,deque既可以用来实现栈(stack)也可以用来实现队列(queue

  • python 多进程队列数据处理详解

    我就废话不多说了,直接上代码吧! # -*- coding:utf8 -*- import paho.mqtt.client as mqtt from multiprocessing import Process, Queue import time, random, os import camera_person_num MQTTHOST = "172.19.4.4" MQTTPORT = 1883 mqttClient = mqtt.Client() q = Queue() # 连

  • python分布式爬虫中消息队列知识点详解

    当排队等待人数过多的时候,我们需要设置一个等待区防止秩序混乱,同时再有新来的想要排队也可以呆在这个地方.那么在python分布式爬虫中,消息队列就相当于这样的一个区域,爬虫要进入这个区域找寻自己想要的资源,当然这个是一定的次序的,不然数据获取就会出现重复.就下来我们就python分布式爬虫中的消息队列进行详细解释,小伙伴们可以进一步了解一下. 实现分布式爬取的关键是消息队列,这个问题以消费端为视角更容易理解.你的爬虫程序部署到很多台机器上,那么他们怎么知道自己要爬什么呢?总要有一个地方存储了他们

  • Python OpenCV使用dlib进行多目标跟踪详解

    目录 1.使用dlib进行多目标跟踪 2.项目结构 3.dlib多对象跟踪的简单“朴素”方法 4.快速.高效的dlib多对象跟踪实现 5.完整代码 6.改进和建议 在本教程中,您将学习如何使用 dlib 库在实时视频中有效地跟踪多个对象. 我们当然可以使用 dlib 跟踪多个对象:但是,为了获得可能的最佳性能,我们需要利用多处理并将对象跟踪器分布在处理器的多个内核上. 正确利用多处理使我们能够将 dlib 多对象跟踪每秒帧数 (FPS) 提高 45% 以上! 1.使用 dlib 进行多目标跟踪

  • python爬虫中多线程的使用详解

    queue介绍 queue是python的标准库,俗称队列.可以直接import引用,在python2.x中,模块名为Queue.python3直接queue即可 在python中,多个线程之间的数据是共享的,多个线程进行数据交换的时候,不能够保证数据的安全性和一致性,所以当多个线程需要进行数据交换的时候,队列就出现了,队列可以完美解决线程间的数据交换,保证线程间数据的安全性和一致性. #多线程实战栗子(糗百) #用一个队列Queue对象, #先产生所有url,put进队列: #开启多线程,把q

  • Python学习之字符串函数使用详解

    目录 1 搜索字符串函数 2 设置字符串格式函数 3 改变字符串大小写函数 4 选定字符串函数 5 拆分字符串函数 6 替换字符串函数 Python的友好在于提供了非常好强大的功能函数模块,对于字符串的使用,同样提供许多简单便捷的字符串函数.Python 字符串自带了很多有用的函数,在字符串函数之前先介绍一个非常实用的dir()内置函数,因为对每一个初学者还是大佬级别的python程序员,都不能完全记住所有方法.而该函数可以查看所有这些函数,可调用 dir 并将参数指定为任何字符串(如 dir(

  • Python内置模块Collections的使用教程详解

    目录 1.模块说明 2. 实战代码 (1) testNamedTuple函数 (2) testDeque函数 (3)testDefaultdict函数 (4) testOrderedDict函数 (5) testCounter函数 1.模块说明 collections 是 Python 的一个内置模块,所谓内置模块的意思是指 Python 内部封装好的模块,无需安装即可直接使用. collections 包含了一些特殊的容器,针对 Python 内置的容器,例如: list.dict.set.t

  • iOS开发探索多线程GCD队列示例详解

    目录 引言 进程与线程 1.进程的定义 2.线程的定义 3. 进程和线程的关系 4. 多线程 5. 时间片 6. 线程池 GCD 1.任务 2.队列 3.死锁 总结 引言 在iOS开发过程中,绕不开网络请求.下载图片之类的耗时操作,这些操作放在主线程中处理会造成卡顿现象,所以我们都是放在子线程进行处理,处理完成后再返回到主线程进行展示. 多线程贯穿了我们整个的开发过程,iOS的多线程操作有NSThread.GCD.NSOperation,其中我们最常用的就是GCD. 进程与线程 在了解GCD之前

  • Android 消息队列模型详解及实例

    Android 消息队列模型详解及实例 Android系统的消息队列和消息循环都是针对具体线程的,一个线程可以存在(当然也可以不存在)一个消息队列(Message Queue)和一个消息循环(Looper).Android中除了UI线程(主线程),创建的工作线程默认是没有消息循环和消息队列的.如果想让该线程具有消息队列和消息循环,并具有消息处理机制,就需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环.如以下代码所示: class

随机推荐