python多进程假死

结论:python多进程间用Queue通信时,如果子进程操作Queue满了或者内容比较大的情况下,该子进程会阻塞等待取走Queue内容(如果Queue数据量比较少,不会等待),如果调用join,主进程将处于等待,等待子进程结束,造成死锁

解决方式:在调用join前,及时把Queue的数据取出,而且Queue.get需要在join前

原理分析

模拟子进程阻塞:

from multiprocessing import Process, Queue


def fun(q):
    num = 1000000000
    q.put('=' * num)
    print("done put")


if __name__ == '__main__':
    queue = Queue()
    p = Process(target=fun, args=(queue,))
    p.start()
    p.join()
    print("done")

原因分析:

#
# Queue type using a pipe, buffer and thread
#

class Queue(object):

multiprocessing.Queue底层是基于Pipe构建,操作系统管道不是无限长,因此子进程在执行put()期间,处于阻塞,直到某些其他进程使用get()从队列中取走数据。上例中,主进程等待子进程,打印不了done

 

而当队列put数据比较少时,是没有问题的,先打印done put,再打印done,但这样写法是有隐患,当put数据比较多时,就会阻塞

from multiprocessing import Process, Queue


def fun(q):
    num = 1
    q.put('=' * num)
    print("done put")


if __name__ == '__main__':
    queue = Queue()
    p = Process(target=fun, args=(queue,))
    p.start()
    p.join()
    print("done")

正确的写法

from multiprocessing import Process, Queue


def fun(q):
    num = 1000000000
    q.put('=' * num)
    print("done put")


if __name__ == '__main__':
    queue = Queue()
    p = Process(target=fun, args=(queue,))
    p.start()
    queue.get()
    p.join()
    print("done")

在join前面调用queue.get

 

注意!!!以下这样写法也是不对的,join要在queue.get前面,不然主进程等待子进程结束,而子进程等待队列数据取走,造成死锁

    p = Process(target=fun, args=(queue,))
    p.start()
    p.join()
    queue.get()

 

Python多线程补充

Python 是一门解释型语言,它的执行是由解释器来控制的。

GIL,全称是 Global Interpreter Lock ,全局解释锁 ,专门给解释器用

一般情况下在用户态下是无法做到线程级别的时间片轮转

但是 python 能做到!python 里,解释器可以记录每一个线程执行了多长时间——时间一到,就能够切换到另一条线程。

GIL 就是拿来给线程加锁的,当一个线程将要执行时,解释器会把 GIL 锁给这个线程,其他线程因为没有锁,是无法运行的。等到持有锁线程阻塞或者运行 100 个字节码,解释器就会把锁交给其他线程

但是这个 GIL 锁是全局(Global)的,也就导致即使是多核情况下,一次也只有一个线程能运行,从整体上看,整个程序是串行的

python多线程应用

拿爬虫程序来说吧,单个爬虫总会花时间在下载网页上,很多 CPU 时间就浪费掉了,提供 sleep 机制后,这些爬虫可以在等待下载时释放 GIL 锁,把机会让给其他爬虫,这样整体运行速度能够得到大幅提升

也就是说 Python 的多线程适合 I/O 密集型的程序,但是对计算密集型程序就不那么友好了

对于计算密集型程序用多进程或者让 python 调用 C 语言的代码,在 C 语言里实现多线程

lovelife110 CSDN认证博客专家 机器学习 中间件 架构
如果我写的文章有帮助,2020年度csdn博客之星,劳请投下票,https://bss.csdn.net/m/topic/blog_star2020/detail?username=qq_33873431
已标记关键词 清除标记
在使用 python 协程下载图片中,最终协程的任务数 卡在 97 一直循环,不知道哪里出了问题,有大佬知道什么情况吗,困扰我好久 下图是我运行结果,附上代码。 ![图片说明](https://img-ask.csdn.net/upload/202005/26/1590492662_13015.png) ``` # -*- coding: utf-8 -*- import requests from lxml import etree import time import os import pandas as pd import asyncio import aiohttp import aiomysql from random import randint import cchardet import aiofiles import logging class sikupicture_Spider(object): def __init__(self): # self.seens_url = [] self.loop = asyncio.get_event_loop() self.queue = asyncio.PriorityQueue() self._workers = 0 # 当前工作数 self._max_workers = 150 # 最大工作数 self.overtime = {} # {url: times,} 记录失败的URL的次数 self.overtime_threshold = 4 self.headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", } self.list_content = [] async def init_url(self): info = pd.read_excel(r"{}".format(os.path.abspath('moban.xlsx'))).fillna('') for ite in info.itertuples(): await self.queue.put((randint(1, 5), getattr(ite, 'url'))) async def fetch(self, session, url, timeout, headers=None, binary=False, proxy=None): _headers = self.headers if headers: _headers = headers try: async with session.get(url, headers=_headers, timeout=timeout, proxy=proxy, allow_redirects=False) as resp: status_code = resp.status if status_code == 403: print("url-403", url) if url in self.overtime: self.overtime[url] += 1 if self.overtime[url] > self.overtime_threshold: pass await self.queue.put((randint(1, 5), url)) else: self.overtime[url] = 1 await self.queue.put((randint(1, 5), url)) status_code = 0 html = None if binary: text = await resp.read() encoding = cchardet.detect(text) html = text.encode(encoding, errors='ignore') else: html = await resp.text() except TimeoutError: print("url-overtime", url) if url in self.overtime: self.overtime[url] += 1 if self.overtime[url] > self.overtime_threshold: pass await self.queue.put((randint(1, 5), url)) else: self.overtime[url] = 1 await self.queue.put((randint(1, 5), url)) status_code = 0 html = None return status_code, html async def download_img(self, session, img_url, timeout, url, headers=None, binary=True, proxy=None): _headers = self.headers if headers: _headers = headers try: async with session.get(img_url, headers=_headers, timeout=timeout, proxy=proxy, allow_redirects=False) as resp: status_code = resp.status if binary: html = await resp.read() else: html = await resp.text() except TimeoutError: print("url-overtime", img_url) if url in self.overtime: self.overtime[url] += 1 if self.overtime[url] > self.overtime_threshold: pass else: await self.queue.put((randint(1, 5), url)) else: self.overtime[url] = 1 await self.queue.put((randint(1, 5), url)) status_code = 0 html = None return status_code, html def parse_source(self, source): try: response_1 = etree.HTML(source) except Exception as err: logging.error(f'parse error:{err}') url = "" else: img_url = response_1.xpath("//a[@href='javascript:;']/@supsrc")[0] if len( response_1.xpath("//a[@href='javascript:;']/@supsrc")[0]) else "" return img_url async def process(self, session, url, timeout): status, source = await self.fetch(session, url, timeout) file_name = url.replace("http://item.secoo.com/", "").replace(".shtml", "") if status == 200: img_url = self.parse_source(source) img_status, img_source = await self.download_img(session, img_url, timeout, url) if img_status == 200: async with aiofiles.open("F:\\dawnzhu\\picture\\"+file_name+".jpg", "wb") as f: await f.write(img_source) self._workers -= 1 print("任务完成", self._workers, "url_status", status, "img_status", img_status) else: self._workers -= 1 print("任务完成", self._workers, "url_status", status,) async def loop_crawl(self): await self.init_url() timeout = aiohttp.ClientTimeout(total=20) conn = aiohttp.TCPConnector(loop=self.loop, limit=50, force_close=True, enable_cleanup_closed=True) session = aiohttp.ClientSession(connector=conn, timeout=timeout) while True: if self._workers >= self._max_workers: print("work 的判断") await asyncio.sleep(5) continue if self.queue.empty(): print("队列是否为空....", self._workers) await asyncio.sleep(5) if self._workers == 0: break continue _, url = await self.queue.get() asyncio.ensure_future(self.process(session, url, timeout)) self._workers += 1 print("队列剩余数量", self.queue.qsize(), self._workers) await session.close() def run(self): try: self.loop.run_until_complete(self.loop_crawl()) except KeyboardInterrupt: self.loop.close() if __name__ == '__main__': sp = sikupicture_Spider() sp.run() ```
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__0809 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值