• python多线程与多进程


    多线程与多进程

    一, 什么是进程, 什么是线程?

    ​ 进程: 运行中的程序. 每次我们执行一个程序, 咱们的操作系统对自动的为这个程序准备一些必要的资源(例如, 分配内存, 创建一个能够执行的线程. )

    ​ 线程: 程序内, 可以直接被CPU调度的执行过程. 是操作系统能够进行运算调度的最小单位. 它被包含在进程之中, 是进程中的实际运作单位.

    ​ 进程与线程之间的关系:

    ​ 进程是资源单位. 线程是执行单位. 就好比是一家公司. 一家公司的资源就是桌椅板凳, 电脑饮水机这些资源, 但是, 我们如果说一家公司正在运转着, 运行着. 那里面必须要有能为这家公司工作的人. 程序里面也一样, 进程就是为了程序运行而需要的各种资源. 但是程序想要运行, 就必须由线程来被CPU调度执行.

    ​ 我们运行的每一个程序默认都会有一个线程. 哪怕是只有helloworld级别的程序. 想要执行. 也会有一个线程产生.

    二, 多线程

    ​ 顾名思义, 多线程就是让程序产生多个线程一起去执行. 还拿公司举例子. 一家公司里如果只有一个员工, 工作效率肯定不会高到哪里去. 怎么提高效率? 多招点儿人就OK了.

    ​ 如何实现多线程, 在python中, 有两种方案实现多线程.

    1. 直接用Thread创建线程

    我们先看看单线程的效果

    def func():
        for i in range(1000):
            print("func", i)
    
    
    if __name__ == '__main__':
        func()
        for i in range(1000):
            print("main", i)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    再看多线程

    from threading import Thread
    
    
    def func():
        for i in range(1000):
            print("func", i)
    
    
    if __name__ == '__main__':
        t = Thread(target=func)
        t.start()
        for i in range(1000):
            print("main", i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2. 继承Thread类

    from threading import Thread
    
    
    class MyThread(Thread):
        def run(self):
            for i in range(1000):
                print("func", i)
    
    
    if __name__ == '__main__':
        t = MyThread()
        t.start()
        for i in range(1000):
            print("main", i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以上两种是最基本的python创建多线程的方案. python还提供了线程池

    3. 线程池

    python还提供了线程池功能. 可以一次性的创建多个线程, 并且, 不需要我们程序员手动去维护. 一切都交给线程池来自动管理.

    # 线程池
    def fn(name):
        for i in range(1000):
            print(name, i)
    
    
    if __name__ == '__main__':
        with ThreadPoolExecutor(10) as t:
            for i in range(100):
                t.submit(fn, name=f"线程{i}")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果任务有返回值怎么办?

    def func(name):
        time.sleep(2)
        return name
    
    
    def do_callback(res):
        print(res.result())
    
    
    if __name__ == '__main__':
        with ThreadPoolExecutor(10) as t:
            names = ["线程1", "线程2", "线程3"]
            for name in names:
                # 方案一, 添加回调
                t.submit(func, name).add_done_callback(do_callback)
    
                
    if __name__ == '__main__':
        start = time.time()
        with ThreadPoolExecutor(10) as t:
            names = [5, 2, 3]
            # 方案二, 直接用map进行任务分发. 最后统一返回结果
            results = t.map(func, names, )  # 结果是按照你传递的顺序来执行的, 代价就是如果第一个没结束. 后面就都没结果
            for r in results:
                print("result", r)
        print(time.time() - start)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4. 多线程在爬虫中的应用

    http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml

    依然用新发地这个案例.

    import requests
    from lxml import etree
    from concurrent.futures import ThreadPoolExecutor
    
    
    def get_page_source(url):
        resp = requests.get(url)
        return resp.text
    
    
    def get_totle_count():
        url = "http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml"
        source = get_page_source(url)
        tree = etree.HTML(source)
        last_href = tree.xpath("//div[@class='manu']/a[last()]/@href")[0]
        totle = last_href.split("/")[-1].split(".")[0]
        return int(totle)
    
    
    def download_content(url):
        source = get_page_source(url)
        tree = etree.HTML(source)
        trs = tree.xpath("//table[@class='hq_table']/tr[position() > 1]")
        result = []
        for tr in trs:
            tds = tr.xpath("./td/text()")
            result.append((tds[0], tds[1], tds[2], tds[3], tds[4], tds[5], tds[6]))
        return result
    
    
    def main():
        f = open("data.csv", mode="w")
        totle = get_totle_count()
        url_tpl = "http://www.xinfadi.com.cn/marketanalysis/0/list/{}.shtml"
    
        with ThreadPoolExecutor(50) as t:
            data = t.map(download_content, (url_tpl.format(i) for i in range(1, totle+1)))
            # 拿到所有任务的返回
            for item in data:
                # 每个任务的数据循环出一行
                for detial in item:
                    # 写入文件
                    content = ",".join(detial) + "\n"
                    print(content)
                    f.write(content)
    
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    三, 多进程

    一个公司能创造的价值毕竟是有限的. 怎么办? 开分公司啊. 此所谓多进程. python实现多进程的方案和多线程几乎一样. 非常的简单

    ###1. 直接用Process创建进程

    def func():
        for i in range(1000):
            print("func", i)
    
    
    if __name__ == '__main__':
        p = Process(target=func)
        p.start()
    
        for i in range(1000):
            print("main", i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 继承Process类

    class MyProcess(Process):
        def run(self):
            for i in range(1000):
                print("MyProcess", i)
    
    
    if __name__ == '__main__':
        t = MyProcess()
        t.start()
        for i in range(1000):
            print("main", i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ###3.多进程在爬虫中的应用

    ​ 我们一般很少直接使用多进程. 最适合使用多进程的情况是: 多个任务需要一起执行. 并且互相之间数据可能有交汇但功能相对独立.比如, 我们自己做一个代理IP池, 就需要从网络上进行抓取, 抓取得到的IP要进行校验才可以进行使用. 此时, 抓取任务和校验任务就相当于完全独立的两个功能. 此时就可以启动多个进程来实现. 再比如, 如果遇到图片抓取的时候, 我们知道图片在一般都在网页的img标签中src属性存放的是图片的下载地址. 此时我们可以采用多进程的方案来实现, 一个负责疯狂扫图片下载地址. 另一个进程只负责下载图片.

    ​ 综上, 多个任务需要并行执行, 但是任务之间相对独立(不一定完全独立). 可以考虑用多进程.

    # 进程1. 从图片网站中提取到图片的下载路径
    def get_pic_src(q):
        print("start main page spider")
        url = "http://www.591mm.com/mntt/"
        resp = requests.get(url)
        tree = etree.HTML(resp.text)
        child_hrefs = tree.xpath("//div[@class='MeinvTuPianBox']/ul/li/a/@href")
        print("get hrefs from main page", child_hrefs)
        for href in child_hrefs:
            href = parse.urljoin(url, href)
            print("handle href", href)
            resp_child = requests.get(href)
            tree = etree.HTML(resp_child.text)
            pic_src = tree.xpath("//div[@id='picBody']//img/@src")[0]
            print(f"put {pic_src} to the queue")
            q.put(pic_src)
            # 作业, 分页图片抓取
            # print("ready to another!")
            # others = tree.xpath('//ul[@class="articleV2Page"]')
            # if others:
    
    
    # 进程2. 从图片网站中提取到图片的下载路径
    def download(url):
        print("start download", url)
        name = url.split("/")[-1]
        resp = requests.get(url)
        with open(name, mode="wb") as f:
            f.write(resp.content)
        resp.close()
        print("downloaded", url)
    
    
    def start_download(q):
        with ThreadPoolExecutor(20) as t:
            while True:
                t.submit(download, q.get())  # 启动
    
                
    def main():
        q = Queue()
        p1 = Process(target=start_download, args=(q,))
        p2 = Process(target=get_pic_src, args=(q,))
        p1.start()
        p2.start()
    
    
    if __name__ == '__main__':
        main()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    【PL理论】(3) F# 数据类型:变量类型 | F# 操作符 | 定义函数 | 使用定义的函数 | if-then-else 表达式:绝对不能省略 else 部分,除非表达式是 unit 类型
    数学分析—极限
    二进制手表
    42-接雨水
    设计模式-原型模式
    医学影像信息(PACS)系统软件源码
    青春无言│用技术定格毕业季最美好的回忆
    嵌入式软件调试(Debug)方法
    【C进阶】之动态内存分配及内存操作函数
    【Linux】开始使用gdb吧!
  • 原文地址:https://blog.csdn.net/jinying_51eqhappy/article/details/133639916