python3多线程通信方式,主要理解队列的join()和task_done()方法

多线程通信方式

共享变量

  1. 创建全局变量,多个线程公用一个全局变量,方便简单。但是坏处就是共享变量容易出现数据竞争,不是线程安全的,解决方法就是使用互斥锁。

    # 示例代码,模拟爬虫
    import threading
    import time
    
    url_lists = []
    
    
    def get_urls():
        # 模拟爬取url
        global url_lists
        print("get urls started")
        for i in range(20):
            url_lists.append(f"https://www.baidu.com/{i}")
        print("get urls end")
    
    
    def get_detail():
        # 模拟爬取页面内容
        global url_lists
        if len(url_lists):
         url = url_lists.pop()
         print("get detail started")
         time.sleep(2)
         print("get detail end")
    
    
    if __name__ == '__main__':
        # 爬取url链接
        thread_get_urls = threading.Thread(target=get_urls)
        thread_get_urls.start()
        # 开启10个线程爬取
        for i in range(10):
            t = threading.Thread(target=get_detail)
            t.start()
    
  2. 将共享变量以参数传递进去

    import threading
    import time
    
    url_lists = []
    
    
    def get_urls(url_lists):
        # 模拟爬取url
        while True:
            print("get urls started")
            for i in range(20):
                url_lists.append(f"https://www.baidu.com/{i}")
            print("get urls end")
    
    
    def get_detail(url_lists):
        # 模拟爬取页面内容
        while True:
            if len(url_lists):
                url = url_lists.pop()
                print("get detail started")
                time.sleep(2)
                print("get detail end")
    
    
    if __name__ == '__main__':
        # 爬取url链接
        thread_get_urls = threading.Thread(target=get_urls, args=(url_lists, ))
        thread_get_urls.start()
        # 开启10个线程爬取
        for i in range(10):
            t = threading.Thread(target=get_detail, args=(url_lists, ))
            t.start()
    
  3. 将共享变量单独放在其他py文件中,应用场景变量很多时能方便管理,比如放在variables.py中

    import threading
    import time
    import variables
    
    
    def get_urls():
        # 模拟爬取url
        url_lists = variables.url_lists
        while True:
            print("get urls started")
            for i in range(20):
                url_lists.append(f"https://www.baidu.com/{i}")
            print("get urls end")
    
    
    def get_detail():
        # 模拟爬取页面内容
        url_lists = variables.url_lists
        while True:
            if len(url_lists):
                url = url_lists.pop()
                print("get detail started")
                time.sleep(2)
                print("get detail end")
    
    
    if __name__ == '__main__':
        # 爬取url链接
        thread_get_urls = threading.Thread(target=get_urls)
        thread_get_urls.start()
        # 开启10个线程爬取
        for i in range(10):
            t = threading.Thread(target=get_detail)
            t.start()
    

队列

线程间使用队列进行通信,因为队列所有方法都是线程安全的,所以不会出现线程竞争资源的情况。Queue常用的方法。

  1. put(item, block=True, timeout=None)

    阻塞方式将item添加进队列中,如果队列满了则一直等待,如果给定了timeout则等待timeout;如果block为Flase,则为非阻塞式,队列满时再添加则直接抛出错误

  2. put_nowait(item)

    非阻塞式添加

  3. get(block=True, timeout=None)

    阻塞式获取,队列为空时,则一直等待,或者等待给定timeout秒

  4. get_nowait()

    非阻塞式获取值

  5. qsize()

    返回队列大小

  6. empty()

    返回布尔值,判断队列是否为空

  7. full()

    返回布尔值,判断队列是否满了

  8. join()

    一直阻塞直到队列中的所有项目都已获取并处理完毕。

    每当任务(示例:未爬取过的url)添加到队列时,未完成任务的计数就会增加。 每当消费者线程(示例:爬取网页内容的函数)调用task_done()以指示检索到该项目并且其上的所有工作都已完成时,计数就会下降。 当未完成任务的数量降至零时,join()取消阻塞

  9. task_done()

    表明以前排队的任务(示例:使用一个url爬取网页内容完成)已完成。
    由队列使用者线程使用。每次调用get()方法从队列中获取任务,如果任务处理完毕,则条用task_done()方法,告知等待的队列(queue.join()这里在等待)任务的处理已完成。
    如果join()当前正在阻塞,则它将在所有项目都已处理后恢复(这意味着已为每个已放入队列的项目收到task_done()调用)。
    如果调用的次数超过队列中放置的项目,则引发ValueError。

最后两个方法,是我开始最不能理解的,后面看了很多博客,大概知道她们的作用。下面我以生产者消费者示例代码演示。代码从另外个哥们那里获取的,但是做了些修改

from threading import Thread
import time
import random
from queue import Queue
from collections import deque

# 创建队列,设置队列最大数限制为3个
queue = Queue(3)


# 生产者线程
class Pro_Thread(Thread):
    def run(self):
        # 原材料准备,等待被生产
        tasks = deque([1, 2, 3, 4, 5, 6, 7, 8])
        global queue
        while True:
            try:
                # 从原材料左边开始生产,如果tasks中没有元素,调用popleft()则会抛出错误
                task = tasks.popleft()
                queue.put(task)
                print("生产", task, "现在队列数:", queue.qsize())

                # 休眠随机时间
                time.sleep(random.random())
            # 如果原材料被生产完,生产线程跳出循环
            except IndexError:
                print("原材料已被生产完毕")
                break
        print("生产完毕")


# 消费者线程
class Con_Thread(Thread):
    def run(self):
        global queue
        while True:
            if not queue.empty():
                # 通过get(),这里已经将队列减去了1
                task = queue.get()
                time.sleep(2)
                # 发出完成的信号,不发的话,join会永远阻塞,程序不会停止
                queue.task_done()
                print("消费", task)
            else:
                break
        print("消费完毕")


# r入口方法,主线程
def main():
    Pro_1 = Pro_Thread()
    # 启动线程
    Pro_1.start()
    # 这里休眠一秒钟,等到队列有值,否则队列创建时是空的,主线程直接就结束了,实验失败,造成误导
    time.sleep(1)
    for i in range(2):
        Con_i = Con_Thread()
        # 启动线程
        Con_i.start()
    global queue
    # 接收信号,主线程在这里等待队列被处理完毕后再做下一步
    queue.join()
    # 给个标示,表示主线程已经结束
    print("主线程结束")


if __name__ == '__main__':
    main()

threading.Thread().join()方法和queue.join)()的区别

线程的join()是主线程等待子线程的执行完毕再执行

队列的join()是主线程等待队列中的任务都消耗完再执行

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 一文读懂Python多线程 1、线程和进程 计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运...
    星丶雲阅读 1,442评论 0 4
  • 进程进程的概念是需要理解的,进程是操作系统中正在运行的一个程序实例,操作系统通过进程操作原语来对其进行调度。操作系...
    zhile_doing阅读 488评论 0 0
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,729评论 0 8
  • 这句话是前两天在宁向东老师的课上 ,宁老师老师的文章标题。 回想自己工作以来,这些年的一些成长历程,做下复盘和...
    悦享同学阅读 1,428评论 0 0