前言

在上一章节,我们阐述了多进程的概念以及如何使用,接下来,我们会介绍Python中的多线程。

进程是资源分配的最小单位,而线程则是程序执行的最小单位。

线程与进程的区别

进程拥有独立的堆栈空间和数据段,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,相比进程更节俭,开销比较小,切换速度也比进程快,效率高。

在通信机制上,进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。

属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同过的进程相互独立。

线程又称为轻量级进程,进程有进程控制块,线程有线程控制块;

线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;

在Python中使用线程

我们只需要使用threading这个高级模块,就可以很方便的对线程进行操作

import threading

from time import ctime,sleep

def music(func):

for i in range(2):

print("I was listening to %s. %s" %(func,ctime()))

sleep(1)

def move(func):

for i in range(2):

print("I was at the %s! %s" %(func,ctime()))

sleep(5)

threads = []

t1 = threading.Thread(target=music,args=(u'东风破',))

threads.append(t1)

t2 = threading.Thread(target=move,args=(u'爱在西元前',))

threads.append(t2)

if __name__ == '__main__':

for t in threads:

t.setDaemon(True)

t.start()

for t in threads:

t.join()

print ("all over %s" %ctime())

输出:

I was listening to 东风破. Thu Jan 3 16:22:24 2019

I was at the 爱在西元前! Thu Jan 3 16:22:24 2019

I was listening to 东风破. Thu Jan 3 16:22:25 2019

I was at the 爱在西元前! Thu Jan 3 16:22:29 2019

all over Thu Jan 3 16:22:34 2019

注意:join() 的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

线程锁

当多线程运行时,因为可以数据段是共享的,因此各个线程都可以访问,但是这样一来会产生一个问题,就是对于资源的变更会造成冲突。

例如对计算器进行累加,如果不对临界区进行控制,则会导致某个线程已经进行了累加操作,另一个线程又重复一次的情况出现。

对于这个问题,在Python中采用线程锁来解决。

count = 0

lock = threading.Lock()

def run_thread(n):

for i in range(100000):

# 获取锁

lock.acquire()

try:

count += 1

except:

pass

# 释放锁

lock.release()

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行。

坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。

其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

小结

线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应。

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。

并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。

需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。