python中的多线程

线程和进程

进程是资源分配的最小单位。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于追踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。进程也可以通过派生(fork或spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息
线程是程序执行的最小单位。线程与进程类似,不过它们是在同一个进程下执行的,并享有相同的上下文。线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫做让步(yielding)
一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线程间的信息共享和通信更加容易。线程一般都是以并发方式执行,正是由于这种并行和数据共享进制,使得多任务间的协作称为了可能

全局解释器锁

Python代码的执行是由Python虚拟机进程控制的,对Python虚拟机的访问是由全局解释器锁(GIL)控制的。尽管Python解释器中可以运行多个线程,但是有个这个锁,在任意给定时刻只有一个线程会被解释器执行

退出线程

当一个线程完成函数的执行时,它就会退出。不建议使用thread模块的一个明显的原因是:在主线程退出之后,所有其他线程都会在没有清理的情况下直接退出,而另一个模块threading会确保在所有"重要的"子线程退出前,保证整个进程的存活

thread模块

thread模块除了派生线程外,还提供了基本的同步数据结构,称为锁对象。其主要的线程方法和锁对象方法有:

thread 模块的方法 描述
start_new_thread(function, args, kwargs=None) 派生一个新的线程,使用给定的args和可选的kwargs来执行function
allocate_lock() 分配LockType锁对象
exit() 给线程退出指令
LockType锁对象的方法 描述
acquire(wait=None) 尝试获取锁对象
locked() 如果获取了锁对象则返回True,否则返回False
release() 释放锁

在下面的例子中,为了实现线程的同步,要对每一个线程都加锁,执行完之后再释放锁,最后一个循环暂停主线程,直到所有锁都被释放后才可以继续执行

import _thread as thread
from time import sleep, ctime

loops = [4, 2]   #sleep时间

def loop(nloop, nsec, lock):
    print("start loop", nloop, "at:", ctime())
    sleep(nsec)
    print("loop", nloop, "done at:", ctime())
    lock.release()   #释放锁,每个线程执行完,都会释放自己的锁

def main():
    print("starting at:", ctime())
    locks = []   #创建一个锁列表
    nloops = range(len(loops))   #range(2)

    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()

threading模块

threading模块提供了更高级别、功能更全面的线程管理。threading模块的Thread类是主要的执行对象,它有thread模块中没有的很多函数

Thread对象数据属性 描述
name 线程名
ident 线程的标识符
daemon 布尔标志,表示这个线程是否是守护线程

| Thread对象方法 | 描述 |
| init(group=None, target=None, name=None, args=(), kwargs={}. verbose=None, daemon=None) | 实例化一个线程对象,需要有一个可调用的target,以及参数args或kwargs,还可以传递name或group参数,不过后者还未实现。此外,verbose标志也是可接受的,而daemon的值会设定thread.daemon属性/标志 |
| start() | 开始执行该线程 |
| run() | 定义线程功能的方法(通常在子类中被应用开发者重写) |
| join(timeout=None) | 直至启动的线程终止之前一直挂起,除非给出了timeout(秒),否则会一直阻塞 |

使用Thread类,可以有很多方法来创建线程。这里说其中的两种方法:

  • 创建Thread的实例,传给它一个函数
  • 派生Thread的子类,并创建子类的实例

创建Thread的实例

在下面的例子中,做了哪些修改呢?使用thread实现的锁没了,取而代之的是一组Thread对象。当实例化每个Thread对象时,把函数(target)和参数(args)传进去,然后得到返回的Thread实例。调用Thread()实例化Thread和调用thread.start_new_thread()的最大区别是新线程不会立即开始执行
当所有线程都分配完成之后,通过调用每个线程的start()方法让它们开始执行,相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,这里只需要为每个线程调用join()方法即可。join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间。使用join()方法要比等待锁释放的无限循环更加清晰。如果主线程还有其他的事情要做,而不是等待这些线程完成,就可以不调用join()

import threading
from time import sleep, ctime

loops = [4, 2]

def loop(nloop, nsec):
    print("start loop", nloop, "at:", ctime())
    sleep(nsec)
    print("loop", nloop, "done at:", 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()

派送Thread的子类

派送出来的子类MyThread继承了父类Thread的构造方法,同时又新增了自己独有的实例属性

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.name = name
        self.func = func
        self.args = args

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


def loop(nloop, nsec):
    print("start loop", nloop, "at:", ctime())
    sleep(nsec)
    print("loop", nloop, "done at:", 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()

还可以对MyThread类进程修改,增加一些调试信息的输出,并将其存储为一个名为myThread的独立模块,以便后面的模块引入调用。除了简单的调用函数外,还可以把结果保存在实例属性self.res,并创建一个新的方法getResult()来获取这个值

import threading
from time import ctime

class MyThread(threading.Thread):

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


    def getResult(self):
        return self.res


    def run(self):
        print("starting", self.name, "at:", ctime())
        self.res = self.func(*self.args)
        print(self.name, "finished at:", ctime())
原文地址:https://www.cnblogs.com/my_captain/p/12830436.html