了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】


作者简介:大学机械本科,野生程序猿,学过C语言,玩过前端,还鼓捣过嵌入式,设计也会一点点,不过如今痴迷于网络爬虫,因此现深耕Python、数据库、seienium、JS逆向、安卓逆向等等,,目前为全职爬虫工程师,学习的过程喜欢记录,目前已经写下15W字电子笔记,因此你看到了下面这篇文章~


技术栈:Python、HTML、CSS、JavaScript、C、Xpath语法、正则、、MySQL、Redis、MongoDB、Scrapy、Pyspider、Fiddler、Mitmproxy、分布式爬虫、JAVA等


个人博客:https://pythonlamb.github.io/


大学作品合集:https://sourl.cn/h9M2jX


欢迎点赞⭐️收藏关注留言呀


登高必自卑,行远必自迩.
我始终坚信越努力越幸运
⭐️ 那些打不倒我们的终将会让我们变得强大
希望在编程道路上深耕的小伙伴都会越来越好


文章目录

    • 多任务介绍
      • 什么是多任务?
      • 单任务与多任务的区别
      • python程序默认是单任务执行
    • 线程——基本使用【重中之重】
      • 什么是线程?主线程与子线程的关系?
      • threading模块的Thread类创建线程(子线程)步骤
    • 线程——查看线程名称、总数量(活跃)【重点】
      • threading.enumerate()函数
    • 线程——线程函数传参、线程执行顺序【重点】
      • 向线程函数传参数方法1——args元组传参
      • 线程函数传递参数方法2——kwargs字典传参
      • 线程函数传递参数方法2——args元组传参以及kwargs字典混合搭配
      • 子线程的执行顺序
    • 线程——守护线程【重点】
      • 什么是守护线程?
      • 子线程设置为守护线程—— 创建的子线程对象.setDaemon(True)
      • pycharm软件按住结束程序按钮显示骷髅头是为什么?
    • 线程——并发和并行【了解】
      • 多任务的底层原理
      • 并发与并行概念及区别
    • 线程——自定义线程类【重中之重】
      • 通过继承 threading . Thread 类来自定义线程类的步骤
    • 线程——多线程之间共享全局变量【重点】
      • 函数中修改全局变量的注意事项
      • 多个线程之间(子线程、主线程)是可以共享全局变量的!
    • 线程——多线程共享一个全局变量产生的问题【重点】
      • 多线程间共享同一个全局变量(同时处理这个全局变量)会产生资源竞争等问题
      • 解决多线程间资源竞争的方法—— join()
    • 线程——同步与异步【重点】
      • 同步与异步
      • 解决多线程同时修改同一个全局变量产生的资源竞争问题(线程锁)
    • 线程——互斥锁【重点】
      • 什么是互斥锁?
      • 创建互斥锁方法—— threading模块的 Lock()类创建互斥锁
    • 线程——死锁【重点】
      • 什么是死锁?
      • 多线程怎么避免死锁情况


多任务介绍

什么是多任务?

多任务就是操作系统同一时间执行多个任务,现在多核CPU已经很普及了,但是即便单核CPU也可以实现多任务(单核CPU采用时间片轮询方法实现多任务,因为CPU没秒计算速度特别快!,2.6GHZ的CPU每秒可运行26亿次)

多任务效率极高,在文件下载、爬虫等应用都很广泛!!

多任务优势

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第1张图片

单任务与多任务的区别

1:多任务是操作系统同一时间执行多个任务,单任务是同一时间执行单个任务

2:多任务相较于单任务效率快,广泛应用在文件下载、python爬虫等

python程序默认是单任务执行

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第2张图片

线程——基本使用【重中之重】

什么是线程?主线程与子线程的关系?

什么是线程?线程可以理解为程序执行的一条分支,也是程序执行流的最小单元,线程是被系统独立调动的和分派的基本单元,线程不拥有自己的系统资源,只拥有一点在运行中必不可少的资源,但是它可以与同属于同一个进程的其他线程共享所拥有的资源

主线程:当一个程序启动时,就会建立一个主进程,这个主进程内又包含了一个主线程,简而言之,系统启动就会建立一个主进程,主进程包含主线程,因此程序启动就会建立一个主线程!!

子线程:子线程是由主线程(程序启动自动生成主线程)创建的,建立之后子线程与主线程一起同时向下执行

理解主线程与子线程的关系:

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第3张图片

主线程重要的方面:

1:主线程创建子线程

2:主线程通常最后执行结束(子线程全部结束执行主线程才结束),打扫战场,如各种关闭操作


threading模块的Thread类创建线程(子线程)步骤

1:导入threading模块

2:利用threading模块的Thread类创建子线程对象

3:利用类的target参数为子线程指定分支任务(例如 target = 函数名,注意函数没有括号)

4:启动创建的子线程,创建子线程对象 . start()

创建步骤代码演示

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第4张图片

注意事项:

1:在利用 Thread 类创建的子线程对象用targrt参数指定任务是函数,且没有括号!!!

2:创建的子线程对象只有调用start()方法,子线程才会执行!

3:主线程只有在所有子线程全部执行结束后才结束执行!

4:创建多个子线程对象,利用start()方法启动,因为计算机运算速度超快,故可看做多个子线程是同时启动运行的!

线程实例应用(唱歌跳舞):

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第5张图片

线程——查看线程名称、总数量(活跃)【重点】

目标:掌握如何查看正在活跃(执行)的线程数量(名称)

threading.enumerate()函数

功能:查看程序中正在活跃的线程数量及其名称,并存放至列表内

语法:thread_list = threading.enumerate( )

注意事项:threading.enumerate()函数只能查看正在活跃(运行)的线程(主线程与子线程)

快速代码体验(查看数量)

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第6张图片

查看线程名称

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第7张图片

线程——线程函数传参、线程执行顺序【重点】

向线程函数传参数方法1——args元组传参

功能:向子线程target指定的函数任务传递参数,因为有的函数是要有参数的

语法:thread_obj = threading.Thread(target = 函数名 , agrs = (函数参数1,函数参数2,函数参数3))

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第8张图片

线程函数传递参数方法2——kwargs字典传参

功能:向子线程target指定的函数任务传递参数,因为有的函数是要有参数的

语法:thread_obj = threading.Thread(target = 函数名 , kwagrs = {“函数参数1”:参数1的值,…….}

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第9张图片

线程函数传递参数方法2——args元组传参以及kwargs字典混合搭配

功能:向子线程target指定的函数任务传递参数,因为有的函数是要有参数的

语法:thread_obj = threading.Thread(target = 函数名 ,args=(参数1,) kwargs = {“函数参数2”:参数2的值,…….}

注意事项:混合传参,也是总共传递那几个参数

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第10张图片

子线程的执行顺序

答:子线程是由系统独立调动的和分派的基本单元,所以子线程的执行顺序是无序的,是cpu决定的,不是程序员决定的

总结:

1:每一个线程(子线程、主线程)都有自己的名字,它们由python自动指定

2:线程的run()方法结束时该线程结束执行

3:我们无法控制线程的执行顺序,但是我们可以通过其他方式影响调度方式

线程——守护线程【重点】

什么是守护线程?

答:即子线程与主线程的一种约定!将子线程设置为守护主线程后,主线程结束运行后,守护线程(子线程)也会自动结束,反之,主线程没有结束运行,守护线程也不会结束运行。可以将主线程比作皇上,设置为守护线程的子线程比作妃子,皇上驾崩(主线程结束),则妃子们全部殉葬(全部守护线程全部结束执行)

答:如果不将子线程设置为守护线程,在主线程意外结束执行后,子线程还会继续执行,这样是缺乏逻辑的,因此要设置守护线程!

子线程设置为守护线程—— 创建的子线程对象.setDaemon(True)

功能:将子线程设置为守护线程,在主线程结束运行后(意外结束等),守护线程(子线程)也全部结束运行!

语法:创建的子线程对象 . setDaemon(True)

注意事项:

1:将子线程设置为守护线程要在 thread.start()之前设置!

2:如果不将子线程设置为守护线程,主线程意外结束后,子线程会继续执行,这样是不允许的!

3:什么情况下主程序执行完毕?就是在if name == ‘main’:这行代码下面除了守护线程的代码外,其余代码均执行完毕即主程序执行结束!!

快速代码体验(没将子线程设置为守护线程的结果

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第11张图片

将子线程设置为守护线程后的结果截图

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第12张图片

pycharm软件按住结束程序按钮显示骷髅头是为什么?

答:因为按下程序结束按钮,主程序结束,但因为多线程,故子线程没有结束执行,子线程还在继续执行,这是没有将子线程设置为守护线程的缘故!!!

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第13张图片

线程——并发和并行【了解】

多任务的底层原理

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第14张图片

并发与并行概念及区别

并发:当操作系统需要执行的任务数大于计算机CPU数量时,计算机通过系统的各种跳读算法(时间片轮询),实现多个任务一起“执行”(其实不是一起执行的,只不过是计算机运行速度飞快,看上去所有的任务一起执行)

并行:操作系统需要执行的任务数小于或等于cpu内核数,一个cpu会最多执行一个任务,即任务真的是一起执行!

注意事项:计算机一般多任务方式都为并发

线程——自定义线程类【重中之重】

目标:通过继承 threading . Thread 类来自定义线程类(应用于多线程下载、爬虫等)

你问我答:已经有现成的子线程类 threading.Thread 可创建子线程,为啥还要自定义线程类创建子线程呢?

答:为了让每个线程的封装更加完美,所以使用 threading 模块时,需要自定义线程类,这样才会更加完美!!!

通过继承 threading . Thread 类来自定义线程类的步骤

1:class新建一个类,并且继承 threading.Thread 父类

2:重写父类 threading.Thread 的 run()方法

3:通过实例化对象(子线程)的 start()方法(继承父类的start()方法)启动这个自定义线程类

注意事项:

1:父类的start()方法只能调用一次

2:子类在重写父类的__init__方法时,一定要先调用父类的__init__方法,即super().__init__继承父类的属性

3:不管是什么问题,只要是子类继承了父类,那么在给子类实例化属性时,一定要继承父类的实例化属性,即super().init( )

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第15张图片

注意事项代码演示:子类在重写父类的__init__方法时,一定要先调用父类的__init__方法,即super().init

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第16张图片

线程——多线程之间共享全局变量【重点】

函数中修改全局变量的注意事项

答:在函数内修改全局变量之前,要先声明这个变量为全局变量,方法为 global 全局变量名

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第17张图片

多个线程之间(子线程、主线程)是可以共享全局变量的!

证明逻辑步骤:

1:定义全局变量
2:定义函数1(子线程1)修改全局变量的值
3:定义函数2(子线程2)读取全局变量的值,看读取的全局变量值,是否是被函数1修改后的全局变量值!是的话就证明全局变量是可以在多线程之间共享的!

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第18张图片

线程——多线程共享一个全局变量产生的问题【重点】

多线程间共享同一个全局变量(同时处理这个全局变量)会产生资源竞争等问题

你问我答:多线程什么情况会产生资源竞争的问题呢?

答:两个(多个)子线程同时处理一个公共资源时(比如说同一个全局变量),就会产生资源竞争的问题

图片详解

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第19张图片

问题代码解释

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第20张图片

解决多线程间资源竞争的方法—— join()

功能:可以有效解决多线程间资源竞争问题,即让一个指定线程先执行完在执行其他线程,即从多线程变为单线程但是会造成程序效率变低

语法:指定线程 . join()

注意事项:

1:子线程的join方法要在子线程的start方法后加入!

2:给某个子线程加入join方法后,这个子线程执行任务时另一个子线程则不启动了,等待这个子线程执行结束,另一个子线程才启动继续执行任务

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第21张图片

线程——同步与异步【重点】

同步与异步

同步:多任务执行时要求有先后顺序,必须一个先执行完毕,另一个线程才能继续执行,只有一个主线!(一个子线程执行,另一个子线程等待这个子线程执行结束,等待期间什么也不做)

异步:多任务执行时没有先后顺序,可以同时执行,存在多条运行主线!

解决多线程同时修改同一个全局变量产生的资源竞争问题(线程锁)

答:可以通过线程同步(同一时间只能有一个线程对全局变量进行修改)的方式(线程锁)来修改多线程间资源竞争问题!思路如下

线程锁机制(同步):两个子线程同时对一个全局变量进行修改,会产生资源竞争问题,采用线程锁机制可以解决此问题,线程锁是在一个子线程修改全局变量时,子线程会对这个全局变量上一把锁,这样其他子线程无法对这个全局变量进行修改,修改完全局变量后,在把锁打开,让其他线程进行修改(线程锁同理)!这就是线程锁机制

图解:

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第22张图片

线程——互斥锁【重点】

什么是互斥锁?

答:互斥锁是将线程同步思想落实的一种机制(锁机制),多个线程几乎同时执行同一任务时(注意一定是同一任务!!!),需要进行同步控制!

互斥锁是最简单的线程同步机制!

互斥锁两种状态:锁定/非锁定

互斥锁实现原理:当一个线程执行某个任务时,该线程对这个任务进行锁定,锁定期间不允许其他线程访问执行这个任务,直到该线程执行完当前任务,将锁打开,变为非锁定状态,其他线程才可以对这个任务进行访问执行,这样就避免了资源竞争问题,互斥锁实现了每次只有一个线程执行任务,保证了多线程情况下数据的正确性

创建互斥锁方法—— threading模块的 Lock()类创建互斥锁

功能:对资源进行锁定以及非锁定,解决多任务(多线程)间资源竞争问题

创建互斥锁步骤:

1:创建互斥锁 mutex = threading . Lock( )
2:对资源上锁 mutex . acquire( )
3:对资源进行解锁 mutex . release( )

注意事项:

1:threading模块下的Lock是一个类

2:子线程访问某个资源竞争任务(全局变量)时,为了避免资源竞争问题,要先对这个资源进行上锁,访问结束进行解锁

3:给多任务加互斥锁时,多个线程全部启动,只是一个线程在处理资源时,另一个线程等待这个资源处理完毕后再次处理,这是和join方法的最大区别(join只启动一个线程)

4:利用互斥锁锁资源时,要尽可能少锁竞争资源(代码)

5:互斥锁应用于多个线程几乎同时执行一个任务时才用互斥锁!!这是使用互斥锁条件!!

快速代码体验

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第23张图片

线程——死锁【重点】

什么是死锁?

答:死锁发生在多线程间,比如两个子线程同时处理一个共同任务,每个子线程都会占用这个任务的一部分资源,且两个子线程间都在等待对方释放其占有的那部分资源,这样就会造成线程死锁(一个子线程将资源锁住,未释放,另一个子线程还在等待这个子线程释放其资源,造成程序无响应、这就叫死锁)

尽管在多线程间死锁很少发生,但是一旦发生就会造成程序无法响应

死锁案例代码截图

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第24张图片

死锁原理截图

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第25张图片

多线程怎么避免死锁情况

答:在函数任务退出之前,就要将锁住的数据进行释放,这样就避免了死锁发生!

上面案例解决办法:将数据释放语句mutex.release(),放在return语句前面执行即可完成退出程序前对锁住的数据进行释放

代码演示

了解多线程并通过Python程序实现多线程解决资源竞争、死锁等问题【非常详细】_第26张图片

未完待续…

你可能感兴趣的:(Python爬虫,Python基础,多任务编程,多线程,python,资源竞争,多任务)