Python 多线程编程,看懂这篇文章就够了

进程

 计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中 的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。 操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。进程也可以通过派生 (fork 或 spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据 栈等,所以只能采用进程间通信(IPC)的方式共享信息。

线程

 线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并 共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷 你进程”。

 线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下 文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫 做让步(yielding)。 一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线 程间的信息共享和通信更加容易。线程一般是以并发方式执行的,正是由于这种并行和数据 共享机制,使得多任务间的协作成为可能。当然,在单核 CPU 系统中,因为真正的并发是不 可能的,所以线程的执行实际上是这样规划的:每个线程运行一小会儿,然后让步给其他线 程(再次排队等待更多的 CPU 时间)。在整个进程的执行过程中,每个线程执行它自己特定 的任务,在必要时和其他线程进行结果通信。

 当然,这种共享并不是没有风险的。如果两个或多个线程访问同一片数据,由于数据访 问顺序不同,可能导致结果不一致。这种情况通常称为竞态条件(race condition)。幸运的是, 大多数线程库都有一些同步原语,以允许线程管理器控制执行和访问。

 另一个需要注意的问题是,线程无法给予公平的执行时间。这是因为一些函数会在完成 前保持阻塞状态,如果没有专门为多线程情况进行修改,会导致 CPU 的时间分配向这些贪婪 的函数倾斜。

Python 中使用线程

thread 模块 ( python2 - thread;python3 - _thread)

>>> import _thread 
>>>

1、不使用线程情况

#!/usr/bin/env python
# -*-coding:utf-8-*-

from time import sleep,ctime

def loop0():
    print('start loop 0 at:',ctime())
    sleep(4)
    print('loop 0 done at:',ctime())

def loop1():
    print('start loop 1 at:',ctime())
    sleep(2)
    print('loop 1 done at:',ctime())

def main():
    print('starting at:',ctime())
    loop0()
    loop1()
    print('all DONE at:',ctime())

if __name__ == '__main__':
    main()

结果:

starting at: Fri Jun 19 18:27:39 2020
start loop 0 at: Fri Jun 19 18:27:39 2020
loop 0 done at: Fri Jun 19 18:27:43 2020
start loop 1 at: Fri Jun 19 18:27:43 2020
loop 1 done at: Fri Jun 19 18:27:45 2020
all DONE at: Fri Jun 19 18:27:45 2020

Process finished with exit code 0

2、使用线程

  • 简单版
#!/usr/bin/env python
# -*-coding:utf-8-*-

import _thread
from time import sleep,ctime

def loop0():
    print('start loop 0 at:',ctime())
    sleep(4)
    print('loop 0 done at:',ctime())

def loop1():
    print('start loop 1 at:',ctime())
    sleep(2)
    print('loop 1 done at:',ctime())

def main():
    print('starting at:',ctime())
    _thread.start_new_thread(loop0,())
    _thread.start_new_thread(loop1,())
    sleep(6)
    print('all DONE at:',ctime())

if __name__ == '__main__':
    main()

结果

starting at: Fri Jun 19 18:41:43 2020
start loop 0 at: Fri Jun 19 18:41:43 2020
start loop 1 at: Fri Jun 19 18:41:43 2020
loop 1 done at: Fri Jun 19 18:41:45 2020
loop 0 done at: Fri Jun 19 18:41:47 2020
all DONE at: Fri Jun 19 18:41:49 2020

Process finished with exit code 0
  • 使用线程和锁
#!/usr/bin/env python
# -*-coding:utf-8-*-

import _thread
from time import sleep,ctime

loops = [4,2]

def loop(nloop,nsec,lock):
    print('start loop {0} at: {1}'.format(nloop,ctime()))
    sleep(nsec)
    print('loop {0} done at: {1}'.format(nloop,ctime()))
    lock.release()

def main():
    print('starting at:',ctime())
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = _thread.allocate_lock()
        lock.acquire()
        locks.append(lock)

    for i in nloops:
        _thread.start_new_thread(loop,(i,loops[i],locks[i]))

    for i in nloops:
        while locks[i].locked():
            pass

    print('all DONE at:',ctime())

if __name__ == '__main__':
    main()

结果

starting at: Tue Jul  7 15:07:45 2020
start loop 0 at: Tue Jul  7 15:07:45 2020
start loop 1 at: Tue Jul  7 15:07:45 2020
loop 1 done at: Tue Jul  7 15:07:47 2020
loop 0 done at: Tue Jul  7 15:07:49 2020
all DONE at: Tue Jul  7 15:07:49 2020

Process finished with exit code 0

3、Python 的 threading 模块

Python 提供了多个模块来支持多线程编程,包括 thread、threading 和 Queue 模块等。程序是可以使用 thread 和 threading 模块来创建与管理线程。thread 模块提供了基本的线程和锁定支持;而 threading 模块提供了更高级别、功能更全面的线程管理。使用 Queue 模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享。

推荐使用更高级别的 threading 模块,而不使用 thread 模块有很多原因。threading 模 块更加先进,有更好的线程支持,并且 thread 模块中的一些属性会和 threading 模块有冲突。 另一个原因是低级别的 thread 模块拥有的同步原语很少(实际上只有一个),而 threading 模块则有很多。

避免使用 thread 模块的另一个原因是它对于进程何时退出没有控制。当主线程结束时,所有其他线程也都强制结束,不会发出警告或者进行适当的清理。如前所述,至少 threading 模块能确保重要的子线程在进程退出前结束。

我们只建议那些想访问线程的更底层级别的专家使用 thread 模块。为了强调这一点,在 Python3 中该模块被重命名为_thread(python2 中为 thread)。你创建的任何多线程应用都应该使用 threading 模块或其他更高级别的模块。

  • 使用 threading,创建 Thread 的实例,传给它一个函数
#!/usr/bin/env python
# -*-coding:utf-8-*-

import threading
from time import sleep,ctime

loops = [4,2]

def loop(nloop,nsec):
    print('start loop {0} at: {1}'.format(nloop,ctime()))
    sleep(nsec)
    print('loop {0} done at: {1}'.format(nloop,ctime()))

def main():
    print('starting at:',ctime())
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = threading.Thread(target=loop,args=(i,loops[i]))
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print('all DONE at:',ctime())

if __name__ == '__main__':
    main()

结果

starting at: Fri Jun 19 18:32:19 2020
start loop 0 at: Fri Jun 19 18:32:19 2020
start loop 1 at: Fri Jun 19 18:32:19 2020
loop 1 done at: Fri Jun 19 18:32:21 2020
loop 0 done at: Fri Jun 19 18:32:23 2020
all DONE at: Fri Jun 19 18:32:23 2020

Process finished with exit code 0
  • 创建 Thred 的实例,传给它一个可调用的类实例
#!/usr/bin/env python
# -*-coding:utf-8-*-

import threading
from time import sleep,ctime

loops = [4,2]

class ThreadFunc(object):

    def __init__(self,func,args,name=''):
        self.func = func
        self.args = args
        self.name = name

    def __call__(self):
        self.func(*self.args)

def loop(nloop,nsec):
    print('start loop {0} at: {1}'.format(nloop,ctime()))
    sleep(nsec)
    print('loop {0} done at: {1}'.format(nloop,ctime()))

def main():
    print('starting at:',ctime())
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__))
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print('all DONE at:',ctime())

if __name__ == '__main__':
    main()

结果与上一个一致

  • 派生 Thread 的子类,并创建子类的实例
#!/usr/bin/env python
# -*-coding:utf-8-*-

import threading
from time import sleep,ctime

loops = [4,2]

class MyThread(threading.Thread):

    def __init__(self,func,args,name=''):
        threading.Thread.__init__(self)
        self.func = func
        self.args = args
        self.name = name

    def run(self):
        print('start {0} {1}'.format(self.name,self.args[0]))
        self.func(*self.args)


def loop(nloop,nsec):
    print('start loop {0} at: {1}'.format(nloop,ctime()))
    sleep(nsec)
    print('loop {0} done at: {1}'.format(nloop,ctime()))

def main():
    print('starting at:',ctime())
    threads = []
    nloops = range(len(loops))

    for i in nloops:
        t = MyThread(loop,(i,loops[i]),loop.__name__)
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print('all DONE at:',ctime())

if __name__ == '__main__':
    main()

结果

starting at: Fri Jun 19 18:36:44 2020
start loop 0
start loop 0 at: Fri Jun 19 18:36:44 2020
start loop 1
start loop 1 at: Fri Jun 19 18:36:44 2020
loop 1 done at: Fri Jun 19 18:36:46 2020
loop 0 done at: Fri Jun 19 18:36:48 2020
all DONE at: Fri Jun 19 18:36:48 2020

Process finished with exit code 0