Python3多线程基础

Python3多线程基础

大家好,我是W

前言:相信现在大家用的比较多的都还是Python3,而且学编程也一定会听说过线程、进程还有python独有的协程,今天就来详细讲解一下Python3的线程由来线程在程序中的作用如何写多线程小脚本如何将多线程运用到项目中

线程的由来

引用:Java并发编程:进程和线程之由来

大家如果有时间的话可以详细看看这篇文章对线程进程的介绍,可以更加深自己对计算机线程进程的理解,对大家面对并发处理并发也有好处。若没有时间可以简单看一下我在下面给大家总结的点:

  1. 我们必须知道计算机组成是由控制器、运算器、IO设备、内存组成的,这是计算机最基本的组成。
  2. 在计算机诞生之初,计算机是用于做科学计算的。但是当时的计算机运行的效率相当之低。除了当时的工艺不如现在之外,还有一个更重要的原因是当时的计算机不能执行多任务。即两个程序不能同时执行,只能A任务执行完毕才能执行B任务,若A在工作时需要大量的IO操作时会怎么办呢?要知道IO操作主要由管道负责,那这时CPU不就闲置了,所以造成了计算资源浪费。
  3. 为解决2中出现的问题,就出现了进程的概念,进程作为计算机分配资源的最小单位存在于计算机之中。即A、B两个程序对应两个进程,其需要运行都需要资源,这时系统会在内存中为两个进程独立地分配资源互不打扰,这样程序就有了各自独立运行的前提。
  4. 同时用户在操作系统中切换进程的时候系统会为切出的进程提供一个保留现场的栈将进程中执行的现场保存起来,待进程切回来时再从栈中弹出来继续执行任务。
  5. 到计算机发展到计算能力比较高的时候,CPU就能够进行轮询了,也就是CPU将自己的时间分成多个时间片,分别取轮询存在于内存中的进程,这样就给了我们计算机同步执行多个程序的假象。

以上都是关于进程的一些知识,接下来就是关于线程的知识:

  1. 在80年代前,计算机中独立分配资源独立执行基本单位就是进程
  2. 但是由于一个程序中往往会有多个子任务,而这些子任务相互独立但是由于进程作为独立执行的基本单位,所以在执行某个子任务的时候整个进程只能顺序执行,这就大大拖延了整个运算时间,所以线程的概念诞生了。
  3. 80年代后,线程作为计算机中独立执行的基本单位存在于计算机中,而进程****依然作为独立分配资源的基本单位。比方:在我们使用某软件和pycharm时出现了更新信息,某软件在点击了更新后整个程序都不能运行了,必须等待程序下载结束,更新完毕才能执行,而pycharm在下载新的安装包或者库时却可以后台执行,这就是因为pycharm在下载东西这一步开了线程。使得pycharm拥有独立地执行基本单位,能够去执行耗时操作,而某程序没开线程,整个程序作为一个进程顺序执行下载操作。

由于上面的讲解可能不是那么清晰,我给大家再贴几篇清晰的、有用的干货文章,希望能够帮到大家更深刻理解:

百度百科-线程

线程的基本概念

线程在程序中的作用

经过上面的了解,我们已经知道线程主要是用于处理一些比较耗时的任务,从而把主线程从耗时任务中解救出来。简单来说这就是线程在程序中的作用,同时我们还要知道主线程和子线程之间的关系:主线程和子线程绑定时,主线程销毁则子线程也会自行销毁。若未绑定,则主线程就算销毁了,子线程还是会执行任务的、同时也是会占用资源。

如何写多线程小脚本

python主要是通过thread和threading这两个模块来实现多线程支持。python的thread模块是比较底层的模块,python的threading模块是对thread做了一些封装,可以更加方便的被使用。但是python(cpython)由于GIL的存在无法使用threading充分利用CPU资源,如果想充分发挥多核CPU的计算能力需要使用multiprocessing模块(Windows下使用会有诸多问题)。
引用:python多线程的原理和实现

  1. 导入threading模块

     import threading
    
  2. 创建一个线程

     t1 = threading.Thread(target=self.multi_func,name='thread1')
     # target=:表示这个线程需要去执行的函数
     # name=:表示这个线程的命名
     # t1:返回值,返回来的是线程
    
  3. 让线程运行

     t1.start()
    
  4. 设置守护线程

     t1.setDaemon(True)
    
     # --------------------------来看源码解释-------------------------------
     @property
     def daemon(self):
     """A boolean value indicating whether this thread is a daemon thread.
        这是一个bool值,表示是否将此线程设置为守护线程
    
     This must be set before start() is called, otherwise RuntimeError is
     raised. Its initial value is inherited from the creating thread; the
     main thread is not a daemon thread and therefore all threads created in
     the main thread default to daemon = False.
    
     这个方法必须要在start()方法前设置,否则会报运行时异常。他的默认值是与创造他的线程来决定的;
     主线程不是守护线程,所以所有在主线程中创建的线程都是非守护线程
    
     The entire Python program exits when no alive non-daemon threads are
     left.
     整个python程序会在没有任何活跃的非守护线程都结束后才结束
    
     """
    

这个守护线程的官方注释不是很长,所以我就给大家翻译了一下,简单来说就是主线程不是守护线程,所以他创建的子线程默认都不是守护线程。若需要设置成守护线程,就要设置这个参数为True。
守护线程:简单来说,守护线程会根据创建自己的父线程决定自己的生命周期,父线程销毁则连带的子线程也销毁了。反过来,若主线程创建了非守护线程,则父线程即使运行到末尾了结束了,但是非守护线程还是在工作,整个python程序也无法正常关闭。

守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点

引用:**[面试官: 谈谈什么是守护线程以及作用 ?](https://www.jianshu.com/p/3d6f32af5625)**

守护线程和非守护线程的使用时看场景的,所以没有优劣之分。

实战讲解(代码复制可用)

简单子线程练习
import threading


def multi_func():
    print('[等待子线程处理的函数]')


if __name__ == '__main__':
    print('111')

# 在main线程中创建子线程t1
# target表示子线程要执行的函数
# name表示子线程的名字 可以不填
t1 = threading.Thread(target=multi_func, name='t1')
# 让子线程运行
t1.start()

print('222')

通过上面的代码我们可以得到输出结果:

111
[等待子线程处理的函数]222
# 在这里可以看到函数与主函数同时输出,所以两个print会打在同一行上,也侧面证明了线程是开启的

Process finished with exit code 0
非守护线程练习
import threading
import time


def multi_funk():
    """
    这个函数负责将交给子线程来处理
    使用while循环来体现实际项目中的耗时操作
    :return:None
    """
    while True:
        time.sleep(1)  # 每隔一秒打印一次
        print(time.time())


if __name__ == '__main__':
    # 开启子线程执行multi_funk函数
    t1 = threading.Thread(target=multi_funk, name='print_time_thread')
    t1.start()

    # 假装主线程处理了一堆东西 消耗时间5秒
    time.sleep(5)
    print('[over]')
    # 处理完毕 主线程结束

输出结果为:

1582119366.6879845
1582119367.689094
1582119368.6896508
1582119369.6906865
[over] # over的输出表示主线程已经结束
1582119370.6918375
1582119371.69242
1582119372.6934965
1582119373.6941245
.....

经过上面的代码运行,我们可以印证关于非守护线程的结论:主线程是非守护线程,所以他创建的子线程也是非守护线程,即即使主线程结束了非守护线程的子线程依然会执行而且占用资源。所以在设置非守护现成的时候需要相当注意资源的回收问题。

守护线程练习
import threading
import time


def multi_funk():
    """
    这个函数负责将交给子线程来处理
    使用while循环来体现实际项目中的耗时操作
    :return:None
    """
    while True:
        time.sleep(1)  # 每隔一秒打印一次
        print(time.time())


if __name__ == '__main__':
    # 开启子线程执行multi_funk函数
    t1 = threading.Thread(target=multi_funk, name='print_time_thread')
    # 将t1这个线程设置为守护线程,当主线程结束时会自动销毁
    t1.setDaemon(True)

    t1.start()

    # 假装主线程处理了一堆东西 消耗时间5秒
    time.sleep(5)
    print('[over]')
    # 处理完毕 主线程结束

输出结果:

1582119592.715045
1582119593.7155328
1582119594.7162395
1582119595.7171578
[over]
# 显然我们在将t1线程设置成守护线程之后,他的生命周期就与主线程生命周期绑定了

Process finished with exit code 0

经过上面的代码,我们可以得到结论:将子线程设置成守护线程,则子线程的生命周期会与父线程绑定父线程销毁则子线程也会销毁

如何将多线程运用到项目中

那么如何将多线程运用到实际的项目中去,接下来我会用一篇实战爬虫多线程项目来讲解。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值