21.多线程1

一、并发和并行
  • 1、多任务的概念
    简单地说,就是操作系统可以同时运行多个任务

  • 2、CPU与多任务的关系
    a.单核CPU可不可以执行多任务?
    单核CPU是可以执行多任务的。由于CPU执行代码都是顺序执行的,所以单核CPU是可以执行多任务的:操作系统会轮流让各个任务交替执行,任务1执行一段时间后,切换给任务2再执行一段时间...这样反复执行下去,每个任务都交替执行。但是,由于CPU的执行速度实在太快了,我们感觉就好像所有任务都在同时执行一样
    注意.CPU的时间周期是根据它的主频来计算的

  • 3、真正的并行执行多任务是如何进行的?
    真正的并行去执行多个任务,只能在多个CPU上实现。但是,由于任务数量往往会远大于
    CPU的核心。所以,操作系统也会自动把多个任务轮流调度到每个CPU核心上去执行

  • 4、并发和并行的区别
    并行:指的是任务数小于或等于CPU核数,这时候任务是可以同时并行运行的
    并发:指的是任务数多于CPU核数,通过操作系统的各种任务调度算法,从而实现多个任务“一起”执行的情况(实际上这些任务都在轮流执行的,只是因为时间太短,给人们造成一种“同时”的错觉)

  • 5、多并发有什么用?
    可以让代码同时执行多个任务,更节省时间

二、同步和异步
  • 同步(同步协调)
    指线程在访问某一资源时,获得了资源的返回结果之后,才可以去执行其他的操作
  • 异步
    指线程在访问某一资源时,无论是否得到这个资源的返回结果,都可以去进行下一步操作,当这个资源有了返回结果之后,系统再自动去通知这个线程(异步回调)
  • 例子:比如说吃饭的场景
    同步:起床 -> 去市场 -> 买菜 -> 煮饭 -> 炒菜 -> 吃饭 -> 学习
    异步:起床 -> 点外卖 -> 学习 -> 收外卖 -> 吃饭 -> 学习
    异步跳过了去买菜到炒菜的过程,在这个时间段可以去学习,外卖到了外卖小哥打电话(异步回调),拿到外卖后就吃饭
三、多线程
  • threading模块介绍
    Python的Thread模块是比较底层的模块,threading模块是对Thread做了一些封装,可以更加方便地使用:threading.Thread

  • Thread类常用方法如下:
    1.创建线程对象
    threading.Thread(target = 函数名, name=自定义线程名, args=(要执行的函数的参数值), kwargs={函数参数值键值对}, ...)
    target:必参,指定线程执行的任务(函数名)
    2.run():用于表示线程活动的方法
    3、start():启动线程活动
    4、join(time):设置主线程等待time秒后,再往下执行;time如果不填写,则默认是当前子线程结束(多个子线程,则进行叠加),常用于对主线程进行阻塞,等待子线程完成后,主线程再进行下面的语句
    5、is_alive():返回线程是否还在活动,开启后是True,开启前和结束后是False(同种作用的过期方法是isAlive())
    6、getName():返回线程名
    7、setName():设置线程名
    8、threading.currentThread():返回当前执行的线程
    9、threading.enumerate():返回正在执行的所有线程(list),不包括启动前和终止后的线程

  • 主线程默认不会等待子线程,不管子线程是否执行完毕,主线程结束完毕,子线程如果还没执行完毕,那么子线程继续执行下去。

  • threading官方文档:https://docs.python.org/zh-cn/3/library/threading.html

四、Thread常用方法的使用
  • 1、对函数使用多线程以及join()的一个使用场景(看主线程的运行时间)
from threading import Thread
import time


def count_time(func):
    """
    计算函数运行时间的装饰器方法
    """

    def warpper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(F'该函数运行了:{end_time - start_time}秒')

    return warpper


def work1():
    print('喝水5秒')
    for i in range(5):
        time.sleep(1)
        print('喝水中...')


def work2():
    print('浇花4秒')
    for i in range(4):
        time.sleep(1)
        print('浇花中...')


@count_time  # 该装饰器主要是为了看main1这个主线程函数的执行时间
def main1():
    """
    最简单的创建线程方式
    """
    t1 = Thread(target=work1)
    t2 = Thread(target=work2)
    t1.start()
    t2.start()
    # 设置主线程等待子线程都结束之后,再往下执行主线程
    t1.join()
    t2.join()

    print('---主线程把main1函数执行完毕---')


main1()

print('---主线程所有函数执行完毕后,函数外面的代码')

'''
不添加join()方法,运行的结果:

喝水5秒
浇花4秒
---主线程把main1函数执行完毕---
该函数运行了:0.00092秒 # 主线程的执行时间,一下子就结束了
---主线程所有函数执行完毕后,函数外面的代码
喝水中...
浇花中...
浇花中...
喝水中...
喝水中...
浇花中...
浇花中...
喝水中...
喝水中...

'''

'''
添加join()方法(time不填写),运行的结果:

喝水5秒
浇花4秒
喝水中...
浇花中...
喝水中...
浇花中...
喝水中...
浇花中...
喝水中...
浇花中...
喝水中...
---主线程把main1函数执行完毕---
该函数运行了:5.009000062942505秒  # 两个子线程+主线程的执行时间
---主线程所有函数执行完毕后,函数外面的代码
'''
多线程图解


  • 2、通过类来定义线程
    1.方法:
    通过看threading的源码可以知道,start()方法调用了run()方法,那么
    可以通过继承Thread类,并重写Thread.run()方法,直接把要进行多线程
    的函数写到这个方法中。接下来线程生成和启动的方法都不变
    2.例子以及join()方法的注意点
from threading import Thread
import requests
import time


def count_time(func):
    """
    计算函数运行时间的装饰器方法
    """

    def warpper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(F'该函数运行了:{end_time - start_time}秒')

    return warpper


class MyThread(Thread):
    """
    定义线程类
    """

    def run(self):
        """
        重写Thread类的run方法
        :return:
        """
        # 百度进行了反爬处理,headers需要带上User-Agent
        headers = {
            "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; "
                          "Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 "
                          "Mobile Safari/537.36 SE 2.X MetaSr 1.0"}
        for i in range(1, 21):  # 发送20次请求
            requests.get(url="https://www.baidu.com", headers=headers)
            print(F'第{i}次发送请求')


@count_time
def main1():
    t = MyThread()  # 一个线程,测试是否成功
    t.start()
    t.join()


# main1()  # 该函数运行了:5.770000219345093秒(20个请求消耗的时间)


# 创建5个线程来执行
@count_time
def main2():
    # 把所创建的线程添加到list中
    thread_list = []
    for i in range(5):
        t = MyThread()  # 创建线程对象
        t.start()  # 开始运行线程
        thread_list.append(t)

        '''
        不能直接在这里t.join(),这样会导致
        一个子线程执行完毕后,再去生成并执行第二个子线程
        这样子得到的时间会是5个单线程发送20个请求的时间的乘积
        '''
        # t.join()

    '''
    # 也不能在这里进行t.join()。因为这里进行等待的是最后一个子线程
    # 如果出现前面的子线程运行比最后的子线程慢的情况,那么得到的结果
    # 也不准确
    '''
    # t.join()

    '''
        最准确的做法
    '''
    # 遍历线程list
    for t in thread_list:
        # 主线程等待子线程执行完毕再执行
        t.join()


main2()  # 该函数运行了:7.66700005531311秒(5*20=100个请求所耗费的时间)
  • 3、给执行多线程的函数传参
    1.如果要执行多线程的任务需要传参数,该如何处理?
    通过看源码,可以发现Thread类的__init__方法的参数中,有args和kwargs参数。并且tun方法中也接收了这两个参数
    2.对函数使用多线程的情况下,给函数传参的例子
import time
from threading import Thread


def work1(name):
    print('喝水5秒')
    for i in range(5):
        time.sleep(1)
        print(F'{name}在喝水中...')


def work2():
    print('浇花4秒')
    for i in range(4):
        time.sleep(1)
        print('浇花中...')


def main():
    # 给work1函数传参数,两种方法

    # 注意:一个参数的元组,别忘了加,号,否则报错
    # t1 = Thread(target=work1, args=('lzl',))

    t1 = Thread(target=work1, kwargs={'name': 'lzl'})

    t1.start()


main()
'''
结果:
喝水5秒
lzl在喝水中...
lzl在喝水中...
lzl在喝水中...
lzl在喝水中...
lzl在喝水中...
'''
  • 自定义Thread类,如果给执行多线程的函数传参?
    1.方法1:重写__init__方法(注意:要调用父类的__init__方法)
    2.方法2:后面可能说
from threading import Thread
import requests


class MyThread(Thread):
    """
    定义线程类
    """

    # 给执行多线程的函数传参,方法1:

    def __init__(self, url):
        super().__init__()
        self.url = url

    def run(self):
        """
        重写Thread类的run方法
        """

        # 百度进行了反爬处理,headers需要带上User-Agent
        headers = {
            "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; "
                          "Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 "
                          "Mobile Safari/537.36 SE 2.X MetaSr 1.0"}
        for i in range(1, 21):  # 发送20次请求
            requests.get(url=self.url, headers=headers)  # 接收传递来的url
            print(F'第{i}次发送请求给{self.url}')


def main1():
    t = MyThread(url="https://www.baidu.com")  # 一个线程,测试是否成功
    t.start()


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

推荐阅读更多精彩内容

  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 一. 操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式: 向下管理硬件,向上提供接口.操作系统进行...
    月亮是我踢弯得阅读 5,950评论 3 28
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 3,525评论 0 5
  • @Author : Roger TX (425144880@qq.com) @Link : https:/...
    Roger田翔阅读 445评论 0 0
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,730评论 0 8