python多线程爬虫入门-进程与线程
CSDN博客地址:https://blog.csdn.net/superDE009/article/details/107816034
序
在编写python爬虫的过程中,有时会碰到数据量太大,python执行太慢的情况。众所周知啊,这正常程序的执行都是顺序执行的,在同一时刻中程序只运行一句语句,其实这是一种十分低效率的方法。那么是否能够让程序运行时同时执行多个任务呢?答案是肯定的,这也就是多线程编程的基本理念。
基本概念
一、线程与进程都是些啥?
在学习多线程编程前,首先我们需要知道什么是线程,以及线程与进程的关系。
1.进程
进程:由程序,数据集,进程控制块三部分组成,它是程序在数据集上的一次运行过程。如果同一段程序在某个数据集上运行了两次,那就是开启了两个进程。进程是资源管理的基本单位,即操作系统会为每个进程分配独立的系统资源。
首先,我们都知道,
一台计算机的
- 硬件为其提供的计算、储存、缓存等资源;
- 操作系统则负责将这些资源调度分配给各个程序;
- 程序运行在操作系统之上,利用操作系统分配的资源进行计算和运行。
在程序开始运行时,首先要向操作系统申请系统资源,在获取系统资源后,利用这些资源开始实际运行(计算或操作数据)。
这样的一个执行过程,即一个进程
在运行完成后,释放系统资源,即进程结束。
由此我们可以看出进程有以下几点特征:
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
2.线程
线程:是进程的一个实体,是CPU调度和分派的基本单位,也是最小的执行单位。它的出现降低了上下文切换的消耗(得益于线程之间共享父进程的资源),提高了系统的并发性,并克服了一个进程只能干一件事的缺陷。线程由进程来管理,多个线程共享父进程的资源空间。
从概念上来讲,线程就相当于进程内部将进程的任务继续细分成多个不同的小任务进行执行,每个线程能够并行执行不同的任务,但是线程没有独立的内存空间,而是使用所在进程的,并且线程之间共享所在进程的内存空间。
即进程获取内存系统资源,其内部的线程获取CPU资源进行任务执行,但是无论是进程还是线程的资源调度都是由操作系统完成的,进程本身不会负责线程的资源调度。
3.线程与进程之间的区别
- 线程是程序执行(CPU资源分配)的最小单位,而进程是操作系统分配内存资源的最小单位;
- 一个进程由一个或多个线程组成,进程的总任务被分为多个小任务,由进程内的线程执行
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
- 调度和切换:线程上下文切换比进程上下文切换要快得多(得益于线程之间内存相互共享)
多线程
讲完了进程和线程的基本概念,接下来我们可以正式进入多线程了。
一、多个线程在CPU中调度运行方式
根据线程的基本概念,我们可知线程是CPU调度的最小单位,众所周知同个CPU核心同时只能运行一个线程,所以在多线程/进程执行时,CPU的资源的调度方式也会有所不同
1、串行
串行十分好理解,在一个CPU核心中,多个线程顺序执行,执行完一个再执行下一个
2、并发
并发是指:多个线程在单个核心运行,同一时间只有一个线程运行,系统不停切换线程运行,看起来像同时运行,实际上是线程不停切换。
即一个线程和另一个线程交错执行,操作系统实现这种交错执行的机制称为:上下文切换。上下文是指操作系统保持跟踪进程或线程运行所需的所有状态信息,如寄存器文件的当前值、主存内容等。
3、并行
并行是指:每个线程都可以分配到独立的CPU核心,以达到真正的多线程同时运行。而不是像并发那样在交替执行
总结:
线程数 | 单核心CPU |
---|---|
单线程 | 串行 |
多线程 | 并发 |
线程数 | 多核心CPU |
---|---|
多线程(线程数量不超过CPU核心数) | 并行 |
多线程(线程数量超过CPU核心数) | 每个核心上皆为并发 |
二、进程线程的三种工作状态
由于电脑的CPU的核心数总是小于电脑运行时开启的总线程数,所以一般情况下,CPU的调度模式皆为并发模式。在并发模式下,线程/进程的工作状态由以下三种。
1.运行状态
当线程/进程获取到CPU资源并开始运行后的状态,即运行状态
(当运行状态的线程的CPU资源被调度给其他线程时,当前线程由运行态转为就绪态)
2.就绪状态
当线程/进程在等待CPU资源时,即为就绪状态
(在并发模式下,线程间交替运行,当一个线程占用CPU时,其他线程皆处于就绪状态)
3.阻塞状态
当一个线程在等待有效输入(可能为人为外部输入,或是其他线程的输入)时,即为阻塞状态。
(当阻塞态的线程等待到有效输入后,该线程由阻塞态转为就绪态)
总结
三、在python3中使用多线程
1.创建启动单个线程
使用多线程,首先我们需要学会创建线程
这里我们使用threading库来创建线程
from threading import Thread
from time import sleep
def test(text):#测试方法
print(text)
sleep(1)
print(text+'done\n')
thread_a = Thread(target=test,args=('Thread_a',))#创建线程
thread_a.start()#启动线程
输出结果为
Thread_a
Thread_a done
在以上代码中,我们通过使用
Thread(target=test,args=('Thread_a',))
语句来创建了一个新的线程
第一个参数target=test
指定了该线程执行的方法,注意这里的方法调用后无()
第二个参数args=('Thread_a',)
是指定了调用方法时传入的参数,每个参数间用,
隔开,若只有一个参数,也要在结尾加入,
否则程序会将整个字符串Thread_a
拆分为字符,将每个字符作为一个参数。
thread_a.start()#启动线程
这个语句就是将我们创建的好的thread_a
线程启动
这里我们只创建了单个线程,所有运行的结果和程序正常运行没有什么区别。
接下来我们使用多个线程执行同一个方法。
2.创建启动多个线程
先上代码
import threading
from threading import Thread
from time import sleep
def test(text):
print(text)
sleep(1)
print(text+'done\n')
thread_a = Thread(target=test,args=('Thread_a',))
thread_b = Thread(target=test,args=('Thread_b',))#新增线程b
thread_a.start()
thread_b.start()#启动线程b
在这段代码中,我新建了一个线程b,该线程于线程a都是执行test
方法,不过是参数不同。
然后启动thread_a
和thread_b
线程。
接下来我们运行
输出结果为:
Thread_a
Thread_b
Thread_a done
Thread_b done
下面用正常顺序运行的程序的输出结果做对比
from time import sleep
def test(text):
print(text)
sleep(1)
print(text+'done\n')
test('thread_a')
test('thread_b')
输出结果为:
thread_a
thread_a done
thread_b
thread_b done
由上下两个程序结果对比,我们可以清楚的看到,使用的多线程的程序在运行时两个线程是同步进行的。而顺序执行的结果明显是先执行完第一次调用,再开始执行第二次调用。
小结
以上这些内容为多线程编程的一些基础概念与知识,之后会有更加进阶的教程,涉及到多线程间通讯(queue)以及多线程运行锁等概念。
系列文章
进程与线程:https://blog.csdn.net/superDE009/article/details/107816034
queue与守护线程:https://blog.csdn.net/superDE009/article/details/107847070
互斥锁和GIL:https://blog.csdn.net/superDE009/article/details/107881944
此篇博客为学习总结,如若有错,还请大佬多多指正。
reference
https://www.cnblogs.com/qianqiannian/p/7010909.html
https://www.cnblogs.com/songzhenhua/p/11824483.html