1.进程和线程的概念

1.1进程

简单的说:进程就是运行着的程序。
我们写的python程序(或者其他应用程序比如画笔、qq等),运行起来,就称之为一个进程在windows下面打开任务管理器,里面显示了当前系统上运行着的进程。


可以看到,我们系统中有很多的进程运行着,比如qq、搜狗输入法等。
这些程序还没有运行的时候,它们的程序代码文件存储在磁盘中,就是那些扩展名为 .exe 文件。
双击它们,这些 .exe 文件就被os加载到内存中,运行起来,成为进程

1.2.主线程概念

而系统中每个进程里面至少包含一个 线程
线程是操作系统创建的,每个线程对应一个代码执行的数据结构,保存了代码执行过程中的重要的状态信息。

没有线程,操作系统没法管理和维护 代码运行的状态信息。

所以没有创建线程之前,操作系统是不会执行我们的代码的。

我们前面写的Python程序,里面虽然没有创建线程的代码,但实际上,当Python解释器程序运行起来(成为一个进程),OS就自动的创建一个线程,通常称为主线程,在这个主线程里面执行代码指令。

当解释器执行我们python程序代码的时候。 我们的代码就在这个主线程中解释执行。

比如:下面这个程序,运行起来后,只有一个线程,就是主线程,在主线程里面,执行代码,顺序下来,一直执行到结束, 主线程就退出了。 同时进程也结束了。

fee = input('请输入午餐费用:')
members = input('请输入聚餐人姓名,以英文逗号,分隔:')# 将人员放入一个列表
memberlist = members.split(',')
# 得到人数
headcount = len(memberlist) # 计算人均费用
avgfee = int (fee) / headcount
print(avgfee)

1.2.3.多线程概念

现代计算机上面,CPU是多核的, 每个核都可以执行代码。

要运行程序里面的代码,操作系统就会分配一个CPU核心去执行该代码。

有的时候,我们希望,能够让更多的CPU核心同时执行我们的程序里面的一些代码。

假如,我们程序里面有个名为 compress 的函数,执行压缩文件的任务。

现在有4个大文件,需要压缩。

如果是一个CPU核心执行这个函数(单线程的程序),压缩一个文件要10秒钟的话, 那么压缩4个文件,就要40秒。

如果我们能够让 4个CPU核心 同时 执行压缩函数, 理论上就只要 10秒。

有的时候, 我们有一批任务要执行,而这些任务的执行时间主要耗费在 非CPU计算 上面。

比如,我们需要到 前程无忧 网站 抓取 python 开发相关的职位信息。

我们要抓取几百个网页的内容, 执行这些抓取信息的任务的代码,时间主要耗费在等待网站返回信息上面。 等待信息返回的时候CPU是空闲的。

如果我们像以前那样 在一个线程里面,用一个循环 依次 获取100个网页的信息,如下

# 抓取 网页的职位信息
def  grabOnePage(url):print('代码发起请求,抓取网页信息,具体代码省略')for pageIdx in range(1,101):url = f'https://search.51job.com/list/020000,000000,0000,00,9,99,python,2,{pageIdx}.html'grabOnePage(url)

就会有很长的时间耗费在 等待服务器返回信息上面。

如果我们能用100个线程,同时运行 获取网页信息的代码, 理论上,可以100倍的减少执行时间。

要让多个CPU核心同时去执行任务,我们的程序必须 创建多个线程 ,让 CPU 执行 多个线程 对应的代码。

2.Python代码中创建新线程

应用程序必须 通过操作系统提供的 系统调用,请求操作系统分配一个新的线程。

python3 将 系统调用创建线程 的功能封装在 标准库 threading 中。

大家来看下面的一段代码

print('主线程执行代码') # 从 threading 库中导入Thread类
from threading import Thread
from time import sleep# 定义一个函数,作为新线程执行的入口函数
def threadFunc(arg1,arg2):print('子线程 开始')print(f'线程函数参数是:{arg1}, {arg2}')sleep(5)print('子线程 结束')# 创建 Thread 类的实例对象, 并且指定新线程的入口函数
thread = Thread(target=threadFunc,args=('参数1', '参数2'))# 执行start 方法,就会创建新线程,
# 并且新线程会去执行入口函数里面的代码。
# 这时候 这个进程 有两个线程了。
thread.start()# 主线程的代码执行 子线程对象的join方法,
# 就会等待子线程结束,才继续执行下面的代码
thread.join()
print('主线程结束')

运行该程序,解释器执行到下面代码时

thread = Thread(target=threadFunc,args=('参数1', '参数2'))

创建了一个Thread实例对象,其中,Thread类的初始化参数 有两个

target参数 是指定新线程的 入口函数, 新线程创建后就会 执行该入口函数里面的代码,

args 指定了 传给 入口函数threadFunc 的参数。 线程入口函数 参数,必须放在一个元组里面,里面的元素依次作为入口函数的参数。

注意,上面的代码只是创建了一个Thread实例对象, 但这时,新的线程还没有创建。

要创建线程,必须要调用 Thread 实例对象的 start方法 。也就是执行完下面代码的时候

thread.start()

新的线程才创建成功,并开始执行 入口函数threadFunc 里面的代码。

有的时候, 一个线程需要等待其它的线程结束,比如需要根据其他线程运行结束后的结果进行处理。

这时可以使用 Thread对象的 join 方法

thread.join()

如果一个线程A的代码调用了 对应线程B的Thread对象的 join 方法,线程A就会停止继续执行代码,等待线程B结束。 线程B结束后,线程A才继续执行后续的代码。

所以主线程在执行上面的代码时,就暂停在此处, 一直要等到 新线程执行完毕,退出后,才会继续执行后续的代码。
join通常用于 主线程把任务分配给几个子线程,等待子线程完成工作后,需要对他们任务处理结果进行再处理。

就好像一个领导把任务分给几个员工,等几个员工完成工作后,他需要收集他们的提高报告,进行后续处理。

这种情况,主线程必须子线程完成才能进行后续操作,所以join就是 等待参数对应的线程完成,才返回。

3.共享数据的访问控制

做多线程开发,经常遇到这样的情况:多个线程里面的代码 需要访问 同一个 公共的数据对象。

这个公共的数据对象可以是任何类型, 比如一个 列表、字典、或者自定义类的对象。

有的时候,程序 需要 防止线程的代码 同时操作 公共数据对象。 否则,就有可能导致 数据的访问互相冲突影响。

请看一个例子。

我们用一个简单的程序模拟一个银行系统,用户可以往自己的帐号上存钱。

对应代码如下:

from threading import Thread,Lock
from time import sleepbank = {'byhy' : 0
}bankLock = Lock()# 定义一个函数,作为新线程执行的入口函数
def deposit(theadidx,amount):# 操作共享数据前,申请获取锁bankLock.acquire()balance =  bank['byhy']# 执行一些任务,耗费了0.1秒sleep(0.1)bank['byhy']  = balance + amountprint(f'子线程 {theadidx} 结束')# 操作完共享数据后,申请释放锁bankLock.release()theadlist = []
for idx in range(10):thread = Thread(target = deposit,args = (idx,1))thread.start()# 把线程对象都存储到 threadlist中theadlist.append(thread)for thread in theadlist:thread.join()print('主线程结束')
print(f'最后我们的账号余额为 {bank["byhy"]}')

Lock 对象的acquire方法 是申请锁。

每个线程在 操作共享数据对象之前,都应该 申请获取操作权,也就是 调用该 共享数据对象对应的锁对象的acquire方法。

如果线程A 执行如下代码,调用acquire方法的时候,

bankLock.acquire()

别的线程B 已经申请到了这个锁, 并且还没有释放,那么 线程A的代码就在此处 等待 线程B 释放锁,不去执行后面的代码。

直到线程B 执行了锁的 release 方法释放了这个锁, 线程A 才可以获取这个锁,就可以执行下面的代码了。

如果这时线程B 又执行 这个锁的acquire方法, 就需要等待线程A 执行该锁对象的release方法释放锁, 否则也会等待,不去执行后面的代码。

4.daemon线程

大家执行下面的代码

from threading import Thread
from time import sleepdef threadFunc():sleep(2)print('子线程 结束')thread = Thread(target=threadFunc)
thread.start()
print('主线程结束')

可以发现,主线程先结束,要过个2秒钟,等子线程运行完,整个程序才会结束退出。

因为:

Python程序中当所有的 非daemon线程 结束了,整个程序才会结束
主线程是非daemon线程,启动的子线程缺省也是 非daemon线程 线程。

所以,要等到 主线程和子线程 都结束,程序才会结束。

我们可以在创建线程的时候,设置daemon参数值为True,如下

from threading import Thread
from time import sleepdef threadFunc():sleep(2)print('子线程 结束')thread = Thread(target=threadFunc,daemon=True # 设置新线程为daemon线程)
thread.start()
print('主线程结束')

再次运行,可以发现,只要主线程结束了,整个程序就结束了。因为只有主线程是非daemon线程。

4.多进程

Python 官方解释器 的每个线程要获得执行权限,必须获取一个叫 GIL (全局解释器锁) 的东西。

这就导致了 Python 的多个线程 其实 并不能同时使用 多个CPU核心。

所以如果是计算密集型的任务,不能采用多线程的方式。

大家可以运行一下如下代码

from threading import Threaddef f():while True:b = 53*53if __name__ == '__main__':plist = []# 启动10个线程for i in range(10):p = Thread(target=f)p.start()plist.append(p)for p in plist:p.join()

运行后,打开任务管理器,可以发现 即使是启动了10个线程,依然只能占用一个CPU核心的运算能力。

如下图所示,我的电脑有4个核心,这个Python进程占用了1个核心的运行能力,所以下图显示25,表示 25% ,也就是 1/4的CPU占用率

如果需要利用电脑多个CPU核心的运算能力,可以使用Python的多进程库,如下

from multiprocessing import Processdef f():while True:b = 53*53if __name__ == '__main__':plist = []for i in range(2):p = Process(target=f)p.start()plist.append(p)for p in plist:p.join()

运行后,打开任务管理器,可以发现 有3个Python进程,其中主进程CPU占用率为0,两个子进程CPU各占满了一个核心的运算能力。

如下图所示

仔细看上面的代码,可以发现和多线程的使用方式非常类似。

还有一个问题,主进程如何获取 子进程的 运算结果呢?

可以使用多进程库 里面的 Manage 对象,如下

from multiprocessing import Process,Manager
from time import sleepdef f(taskno,return_dict):sleep(2)# 存放计算结果到共享对象中return_dict[taskno] = tasknoif __name__ == '__main__':manager = Manager()# 创建 类似字典的 跨进程 共享对象return_dict = manager.dict()plist = []for i in range(10):p = Process(target=f, args=(i,return_dict))p.start()plist.append(p)for p in plist:p.join()print('get result...')# 从共享对象中取出其他进程的计算结果for k,v in return_dict.items():print (k,v)

Python多线程介绍及实例相关推荐

  1. python多线程爬虫实例-Python实现多线程爬虫

    编辑推荐: 本文主要介绍对Python多线程爬虫实战的整体的思路,希望对大家有帮助. 本文来知乎,由火龙果软件Alice编辑,推荐. 最近在写爬虫程序爬取亚马逊上的评论信息,因此也自学了很多爬虫相关的 ...

  2. python多线程爬虫实例-Python多线程爬虫简单示例

    python是支持多线程的,主要是通过thread和threading这两个模块来实现的.thread模块是比较底层的模块,threading模块是对thread做了一些包装的,可以更加方便的使用. ...

  3. python多线程爬虫实例-python支持多线程的爬虫实例

    python是支持多线程的, 主要是通过thread和threading这两个模块来实现的,本文主要给大家分享python实现多线程网页爬虫 一般来说,使用线程有两种模式, 一种是创建线程要执行的函数 ...

  4. python多线程爬虫实例-python多线程爬虫实例讲解

    Python作为一门强大的脚本语言,我们经常使用python来写爬虫程序,简单的爬虫会写,可是用python写多线程网页爬虫,应该如何写呢?一般来说,使用线程有两种模式,一种是创建线程要执行的函数,把 ...

  5. Python 多线程、利用request使用代理、利用递归深度抓取电影网页的内容并将电影的介绍和下载链接保存到mysql中

    本文仅为学习python过程的一个笔记,其中还有一些bug! 还请各位大佬赐教 有些专业的说法还不是很熟悉,欢迎各位大佬帮忙指出 本人时一个新晋奶爸,而立之年突然想转业,想学习python 先介绍一个 ...

  6. python多线程爬虫实例-Python3多线程爬虫实例讲解代码

    多线程概述 多线程使得程序内部可以分出多个线程来做多件事情,充分利用CPU空闲时间,提升处理效率.python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点, ...

  7. python 多线程 模块_Python多线程threading和multiprocessing模块实例解析

    本文研究的主要是Python多线程threading和multiprocessing模块的相关内容,具体介绍如下. 线程是一个进程的实体,是由表示程序运行状态的寄存器(如程序计数器.栈指针)以及堆栈组 ...

  8. [转载] python中pass的使用_Python pass详细介绍及实例代码

    参考链接: Python pass语句 Python pass详细介绍及实例代码 Python pass的用法: 空语句 do nothing 保证格式完整 保证语义完整 以if语句为例,在c或c++ ...

  9. python多线程操作_python多线程操作实例

    一.python多线程 因为CPython的实现使用了Global Interpereter Lock(GIL),使得python中同一时刻只有一个线程在执行,从而简化了python解释器的实现,且p ...

最新文章

  1. Mybatis报错:无效的列类型
  2. 中商惠民李超:500,000+ 便利店背后的精细化管理
  3. Day11多态部分-6 【1.4 多态的应用以及注意事项】
  4. [C # 读书笔记]interface 接口 abstract
  5. C#LeetCode刷题之#34-在排序数组中查找元素的第一个和最后一个位置(Find First and Last Position of Element in Sorted Array)
  6. ScrollView各属性,及代理方法汇总
  7. 浙大PAT的大量感悟
  8. 洛谷 1541 乌龟棋——dp
  9. 当世界从移动优先变为AI优先,未来企业竞争将赢在“维度”
  10. Coding and Paper Letter(四十八)
  11. python运维脚本简书_Python运维篇:会Python的运维工程师价值多少?
  12. qqkey获取原理_通过call获取qqkey支持最新版
  13. 也谈免拆机破解中兴B860av1.1(解决不能安装软件/解决遥控)
  14. JS输入银行卡号,4位自动加空格 ,根据银行卡号获取开户行和银行
  15. php基础知识速记,电气工程师5大背诵妙招速记基础知识
  16. 浏览器显示json格式
  17. vue如何区别浏览器刷新和关闭
  18. photoshop放大缩小有什么快捷键
  19. ubuntu18安装搜狗拼音
  20. stm32使用XR20M1172详细攻略·SPI转UART串口

热门文章

  1. iOS开发 tabBarController选中状态
  2. eclipse 项目 无法 rename
  3. 全面解析Java的垃圾回收机制
  4. Cordiality ERP MVC 3 测试作品
  5. 计算机网络第六章ppt课件,计算机网络与通信(第6章).ppt
  6. Bitcoin0.21版 公链开发(5) PHP集成到Apache中(windows平台)
  7. [Zer0pts2020]ROR
  8. GO语言实现RSA 加密和解密的实现
  9. 关于CVE-2019-0708 - 数组越界
  10. WIN10+MinGW中文输出乱码的解决方法