1、进程和线程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。举个例子来说,打开任务管理器,面板中会显示多个正在执行的程序,这些程序之间是独立执行的,互不影响,这就是进程。
在当代面向线程设计的计算机结构中,进程是线程的容器,线程是进程中的最小执行单位,进程的执行依赖线程,并且一个进程中至少存在一个线程。
那么怎样理解多线程呢?其中有两个重要的概念:串行和并行。提到这个概念,第一时间容易想到物理中的电阻串联和并联,其实有相似之处。将程序要执行的任务视为电阻,那么串行就相当于任务A和任务B串联,电流只能先流过A再流过B,只能按顺序执行。
而并行相当于任务A和任务B并联,电流可同时流过A和B,也就是说程序执行时,任务A和任务B是同时执行的。举个例子,在app听《花海》,同时还可以评论、浏览其它作品等。
2、多线程的适用场景
理解了上述的串行和并行的概念,自然就明晰了并行任务更适合适用多线程。常见的任务主要分为计算密集型和I/O密集型任务,由于计算密集型任务的执行是有逻辑顺序的,比如要计算(1+1)*2,必须先计算加法结果得到2,再计算乘法得到最终的结果4。那么这种场景下,就算使用多线程,由于计算逻辑顺序的存在,也不会提升执行效率。而另一种I/O密集型任务,单个任务的执行之间互不影响,没有逻辑依赖性,适合多线程的应用场景。比如多个文件的下载,多个文件可同时下载,下载效率会大大提升。
3、多线程的使用
python中提供了threading模块以供使用创建多线程程序。码!MyThread继承Thread类,并重写了run方法为一个倒计时过程。
from threading import Thread, Lock
import time
class MyThread(Thread):
def __init__(self, task):
super(MyThread, self).__init__()
self.task = task
def run(self) -> None:
print('task', self.task)
print('2s')
time.sleep(1)
print('1s')
time.sleep(1)
print('0s')
time.sleep(1)
print('sub-thread terminated!')
(1) 创建并开启多线程:
t1 = MyThread('t1')
t2 = MyThread('t2')
t1.start()
t2.start()
如果打断点进行debug,会发现前两行创建线程并初始化,后两行执行run方法,debug可以控制线程的执行。执行结果:任务t1和任务t2几乎是同时执行的。
(2) 线程守护
一个执行程序中,创建的多线程为子线程,还有一个主线程,负责整个程序的执行,线程守护是为了协调主线程和子线程之间的关系。如下创建线程t3,设置为守护线程。
t3 = MyThread('t3')
t3.setDaemon(True)
t3.start()
print('main thread terminated!')
执行结果中,主线程结束时,子线程也全部结束,无论是否执行完毕。
如下创建线程t4,设置为守护线程,并添加到线程等待中。
t4 = MyThread('t4')
t4.setDaemon(True)
t4.start()
t4.join()
print('main thread terminated!')
执行中,主线程会等待所有子线程执行完毕之后再结束。
(3)线程安全
在使用多线程时,线程之间存在数据共享,例如多线程共享全局变量。加入一个线程在从前往后的读取一个元素全为0的列表,而另一个线程同时在从后往前修改列表的元素为1。这样线程一本该输出全为0,但因为线程二对数据的修改,输出了不同的数据,这就造成了数据安全问题,类似于sql数据库中的dirty read。样例:声明一个全局变量k,初始值为0,任务1负责对k进行加1修改,任务2负责打印k,采用普通的方式创建两个线程并开启,输出结果均为3。
K = 0
def task1():
global K
for i in range(3):
K += 1
print('task1 k: %d' % K)
def task2():
global K
print('task2 k: %d' % K)
## 多线程共享全局变量
t5 = Thread(target=task1)
t5.start()
t6 = Thread(target=task2)
t6.start()
要解决线程的安全问题,需要借助线程锁来对线程进行管理。线程锁能够将一个线程锁定,其它线程只能等到该线程执行完毕之后再继续执行。代码中新建一个任务3,带有参数线程锁,并对全局变量修改后进行加锁。main函数中创建了线程锁,并创建了20个线程添加到线程等待。
N = 100
def task3(lock):
global N
lock.acquire(blocking=True, timeout=-1)
for i in range(10):
N -= 1
print(N)
lock.release()
def task4():
global N
for i in range(5):
N += 10
print('*******'*4, N)
def main():
lock = Lock()
tp = []
t7 = Thread(target=task3, args=(lock, ))
t8 = Thread(target=task4)
t7.start()
t8.start()
不加锁执行结果:
加锁执行结果:线程t7执行完之后,线程t8执行。
才疏学浅,欢迎指正!