Hello,大家好。本期来和大家一起学习一下 线程的相关知识。
基础知识补充
首先,我们要了解一下并发和并行。
并发
并发是针对单核 CPU 提出的,指一个处理器同时处理多个任务。
![](https://img-blog.csdnimg.cn/img_convert/f8a3609a85b3a14ed2eb4ffd3f2cc071.png)
相信大家看到这,有些疑惑?图中CPU在一个工作时间点,不是只在处理一个任务吗?为什么会同时处理多个任务?
就让我再举一个贴近大家生活的例子
假设我们的电脑配置为单核CPU,电脑正在运行四个程序:QQ、微信、浏览器、腾讯会议。
![](https://img-blog.csdnimg.cn/img_convert/9ef5d029b6073188951f33136aac76b1.png)
按照我们正常使用逻辑,会发现这四个程序是同时运行的。
那么其中的原理是怎样的呢?
![](https://img-blog.csdnimg.cn/img_convert/31f9d08adc7ff2ddcec39b27e9cdb594.png)
从图中可以发现,CPU先处理QQ一小段时间,然后退出程序再处理微信一小段时间,. . . ,最后退出程序再处理微信一小段时间。这种方式也叫做轮训。
原来虽然 单核CPU 在同一时刻只能执行一个任务,但是通过将 CPU 的使用权在恰当的时机分配给不同的任务,使得多个任务在视觉上看起来是一起执行的。CPU 的执行速度极快,多任务切换的时间也极短,用户根本感受不到,所以并发执行看起来才跟真的一样。
并行
并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行,多个任务”
![](https://img-blog.csdnimg.cn/img_convert/2937536a5cee3865a45d788cc5c26e91.png)
多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行。
双核 CPU 执行两个任务时,每个核心各自执行一个任务,和单核 CPU 在两个任务之间不断切换相比,它的执行效率更高。
并发+并行
在图2中,执行任务的数量恰好等于 CPU 核心的数量,是一种理想状态。但是在实际场景中,处于运行状态的任务是非常多的,尤其是电脑和手机,开机就几十个任务,而 CPU 往往只有 4 核、8 核或者 16 核,远低于任务的数量,这个时候就会同时存在并发和并行两种情况:所有核心都要并行工作,并且每个核心还要并发工作。
例如一个双核 CPU 要执行四个任务,它的工作状态如下图所示:
![](https://img-blog.csdnimg.cn/img_convert/9ccfc02ee24933ee02ecd9a4d2390df0.png)
每个核心并发执行两个任务,两个核心并行的话就能执行四个任务。当然也可以一个核心执行一个任务,另一个核心并发执行三个任务,这跟操作系统的分配方式,以及每个任务的工作状态有关系。
总结
并发针对单核 CPU 而言,它指的是 CPU 交替执行不同任务的能力;并行针对多核 CPU 而言,它指的是多个核心同时执行多个任务的能力。
单核 CPU 只能并发,无法并行;换句话说,并行只可能发生在多核 CPU 中。
在多核 CPU 中,并发和并行一般都会同时存在,它们都是提高 CPU 处理任务能力的重要手段。
线程
概念
线程是cpu的最小执行单位,是计算机cpu的一次调度执行。
线程的五种状态:
新建状态(new)
就绪状态(Runnable)
运行状态(Running)
阻塞状态(Blocked)
死亡状态(Dead)
单线程
import time
def sing():
for i in range(5):
print('正在唱歌...')
time.sleep(1)
def dance():
for i in range(5):
print('正在跳舞...')
time.sleep(1)
def main():
sing()
dance()
if __name__ == '__main__':
main()
'''
正在唱歌...
正在唱歌...
正在唱歌...
正在唱歌...
正在跳舞...
正在跳舞...
正在跳舞...
正在跳舞...
'''
多线程
python完成多任务,需要使用python的内置包:threading
1)创建线程对象:
t1 = threading.Thread(target=sing)
2)运行线程:
t1.start()
3)线程等待:
t1.join()
4)查看当前运行的线程:
print(threading.enumerate())
5)查看线程名称
print(threading.current_thread().name)
import time
import threading
def sing():
for i in range(5):
print('正在唱歌')
time.sleep(1)
def dance():
for i in range(5):
print('正在跳舞')
time.sleep(1)
def main():
# 1.创建线程对象
"""
在实例化线程对象时,需要指定当前创建的线程对象要运行什么任务?
通过target参数指定一个任务(地址),args以元组的形式传参。
"""
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
# 2.内部创建线程并运行
t1.start()
t2.start()
if __name__ == '__main__':
main()
'''
正在唱歌...
正在跳舞...
正在唱歌...
正在跳舞...
正在唱歌...正在跳舞...
正在跳舞...
正在唱歌...
'''
交替执行,两个一起出来
特点:在线程执行的过程中,执行线程的顺序时随机的。在代码运行时,只用一个线程被执行。
多线程存在的问题
(问题一)线程间共享全局变量 --> 资源竞争问题
共享全局变量案例:
import threading
# 定义全局变量
g_num = 100
def test1():
global g_num
g_num += 1
print('test1:%d' % g_num)
def test2():
print('test2:%d' % g_num)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
print('main Thread:%d' % g_num)
if __name__ == '__main__':
main()
"""
test1:101
test2:101main Thread:101
"""
资源竞争问题
当单个线程任务量较大时,就会出现资源竞争问题
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print('test1:%d' % g_num)
def test2(num):
global g_num
for i in range(num):
g_num += 1
print('test2:%d' % g_num)
def main():
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(2)
print('main Thread:%d' % g_num)
if __name__ == '__main__':
main()
"""
test1:1414335
test2:1747168
main Thread:1747168
"""
原因分析
在程序中进行数据的运算会分为好几步:1.获取全局变量的值 2.对当前这个值 +1 3.将得出的结果赋值给全局变量
线程1在去执行计算时得出的结果还没有来得及赋值就进行了任务切换
问题解决:使用互斥锁解决资源竞争问题
1)声明一个互斥锁锁变量
mutex = threading.Lock()
2)上锁
mutex.acquire()
3)释放锁
mutex.release()
互斥锁案例:
import time
import threading
g_num = 0
# 声明一个互斥锁锁变量
mutex = threading.Lock()
def test1(num):
global g_num
for i in range(num):
# 上锁
mutex.acquire()
g_num += 1
# 释放锁
mutex.release()
print('test1: %d' % g_num)
def test2(num):
global g_num
for i in range(num):
# 上锁
mutex.acquire()
g_num += 1
# 释放锁
mutex.release()
print('test2: %d' % g_num)
def main():
t1 = threading.Thread(target=test1, args=(1000000,))
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(2)
print('main Thread: %d' % g_num)
if __name__ == '__main__':
main()
'''
test1: 1936038
test2: 2000000
main Thread: 2000000
'''
(问题二)使用互斥锁 --> 死锁问题
死锁案例:
import threading
import time
class MyThread1(threading.Thread):
def run(self):
# 对mutexA上锁
mutexA.acquire()
# mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
print(self.name+'----do1---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
mutexB.acquire()
print(self.name+'----do1---down----')
mutexB.release()
# 对mutexA解锁
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
# 对mutexB上锁
mutexB.acquire()
# mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
print(self.name+'----do2---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
mutexA.acquire()
print(self.name+'----do2---down----')
mutexA.release()
# 对mutexB解锁
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
解决死锁的最好方法就是:只声明一把锁。
线程池
为什么要学线程池?
因为手动开启线程比较麻烦
如果线程数量过多会导致系统卡顿
所以我们可以创建一个指定数量的线程池来创建线程。
使用到的python第三方库为:
from concurrent.futures import ThreadPoolExecuto
1)创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
2)向线程池提交一个task,10作为action()函数的参数
futrue = pool.submit(action, 10)
3)判断future代表的任务是否结束
print(futrue1.done())
4)查看futrue代表的任务返回的结果
print(futrue1.result())
5)关闭线程池
pool.shutdown()
线程池案例:
from concurrent.futures import ThreadPoolExecutor
my_sum = 0
# 任务函数
def action(max):
global my_sum
my_sum += max
print(my_sum)
# 创建一个包含2条线程的线程池
with ThreadPoolExecutor(2) as f:
for i in range(1, 101):
# 向线程池提交一个任务,i作为action()函数的参数
f.submit(action, max=i)
关于线程的更多用法,欢迎小伙伴后台留言哦。