深入理解python多线程编程
简介
多线程是一种利用计算机多核心处理器的技术,可以将一个进程分成多个线程并行处理。在Python中,多线程编程可以通过threading模块来实现。本篇攻略将从以下几个方面深入理解Python多线程编程:
- 了解线程的概念与原理
- 学习Python中的多线程编程模块
- 编写多线程程序的技巧与注意事项
线程的概念与原理
什么是线程?
线程(Thread)是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享该进程所拥有的全部资源。
线程的优点
相比于进程,线程的优点主要在于以下几个方面:
- 线程共享内存空间,减少了进程间通信(IPC,Inter-Process Communication)的代价。
- 线程创建、销毁、切换的开销很小。
- 线程能跑在较小的堆栈中,减少了内存开销。
- 线程的并发性比进程高,便于实现多任务。
线程的原理
在操作系统中,每个线程都是由一个线程控制块(TCB,Thread Control Block)来表示,并根据其状态在就绪、阻塞、执行等多个状态之间转换。同时,操作系统在调度线程时需要保存当前线程的执行现场,包括线程的程序计数器、寄存器集合、内存状态等。线程执行完毕后,还要恢复之前的执行现场,并执行操作系统指定的下一个线程。
Python中的多线程编程
Python中的多线程编程主要使用threading模块,这个模块提供了Thread类和一些用于多线程编程的方法。下面介绍一下Thread类的几个主要方法:
start()
:启动线程。run()
:线程被调度后执行的方法。join()
:等待线程执行结束。isAlive()
:判断线程是否在执行状态。
下面是一个简单的示例,创建两个线程分别打印出字符和数字:
import threading
import time
def print_char():
for i in range(10):
print(chr(ord('a')+i), end='')
time.sleep(0.1)
def print_numbers():
for i in range(10):
print(i, end='')
time.sleep(0.2)
t1 = threading.Thread(target=print_char)
t2 = threading.Thread(target=print_numbers)
t1.start()
t2.start()
t1.join()
t2.join()
print('all threads have finished.')
上面的代码中,分别创建了两个线程t1和t2,并使用Thread类的target参数指定了这两个线程要执行的函数。然后调用start()方法启动线程,最后使用join()方法等待线程结束。这里因为要先打印字符再打印数字,所以使用time.sleep()方法来控制线程的执行顺序。运行上述代码可以看到,先打印出了字符,再打印出了数字。
多线程编程技巧与注意事项
在多线程编程中,需要注意以下技巧和问题:
1. 线程安全
在多线程编程中,不同的线程可能会共享同一个资源,这就可能导致数据不一致的问题。所以在多线程编程中,需要保证线程安全。常用的解决办法有以下几种:
- 加锁:对临界区代码块进行加锁控制,使得同一时间只有一个线程能够访问该代码块,避免了资源竞争问题。
- 使用Queue:Python中的Queue模块提供了多种数据结构,如Queue、LifoQueue、PriorityQueue等,这些数据结构都是线程安全的,能够避免数据竞争的问题。
2. 线程数量控制
由于线程创建的数量过多可能会导致线程的切换频繁,降低执行效率,所以需要控制线程的数量。
3. 死锁问题
当两个或更多线程在执行过程中被无限期地(或长时间)阻塞、互相等待救援时,我们称这种情况为死锁。在多线程编程中,死锁问题需要特别注意。
简单示例
下面是一个使用Queue队列来控制线程个数的示例,这个示例中,我们用多线程的方式下载一些图片,并保存到本地:
import threading
import queue
import requests
from PIL import Image
from io import BytesIO
def download_img(url, q):
response = requests.get(url)
img = Image.open(BytesIO(response.content))
img.save('./images/{}.jpg'.format(url.split('/')[-1]))
q.get()
def main():
urls = ['https://picsum.photos/300/200/?image={}'.format(i) for i in range(10)]
q = queue.Queue(maxsize=3)
threads = []
for url in urls:
t = threading.Thread(target=download_img, args=(url, q))
threads.append(t)
for t in threads:
while q.full():
pass
q.put(1)
t.start()
for t in threads:
t.join()
print('All images have been downloaded.')
if __name__ == '__main__':
main()
上面的示例中,我们使用了队列模块queue来控制线程数量。首先创建了队列q,最大容量为3。然后依次遍历每个url,创建对应的线程t,并将线程加入threads列表中。然后使用while循环等待队列未满,再将一个计数器(这里是1)放进队列q里面,最后启动线程t。当线程t完成任务后,从队列q中获取一个计数器。最后,调用join()方法等待所有线程结束,输出"All images have been downloaded."。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:深入理解python多线程编程 - Python技术站