「Python-StandardLib」第十六章:并发执行( Cocurrent Executing,线程、多线程队列、子进程)

参考链接:
python多线程
python线程——基于线程的并行

16.1 线程(threading)

ps: python ver. is 2.7.18

线程是一项将非连续依赖任务进行分解的技术。线程能提高应用的响应能力,它接收用户的输入而其他任务放在后台运行。一个相关的用例是:进行I/O交互的同时也在进行计算(cpu执行命令,被称作计算)工作。

16.1.1 线程对象:threading.Thread

在python2.7版本中,线程是通过类创建的,一个类实例对应一个独立的线程。且threading模块是基于thread模块的高水平实现。

类对象代表运行在一个独立的线程中的活动。可以通过两种方式来指定活动:传递一个可调用对象到constructor方法
,或者在一个子类中重写run方法(除了constructor和run可以被覆盖,其他子类中的任何方法都不允许被覆盖;换句话讲,只能覆盖类中的__init__()和run()方法)。

一旦线程被创建,线程活动必须通过调用线程的start()方法来启动,start()方法在单独的控制线程中调用run()方法。

一旦线程活动开始,那么该线程的状态将被视作’alive’,当run()方法终止时(包括正常终止/引发未处理异常)该’alive’状态停止。可以通过调用is_alive()方法判断线程活动是否处理alive状态。

其他线程可以调用线程的join()方法,它会阻塞当前调用的线程直到调用join()方法的线程终止。

每个线程都有一个名字,名字可以被传递到constructor,通过name进行读取或修改。

线程可以被标记为"daemon"线程,其意义是当只剩daemon线程时,整个python程序才会退出。它的初始值由创建线程时继承。标志可以通过daemon属性设置。

Note : Daemon threads are abruptly stopped at shutdown. Their resources (such as open files, database transactions, etc.) may not be released properly. If you want your threads to stop gracefully, make them non-daemonic and use a suitable signalling mechanism such as an Event.

除此之外,还有一个“主线程”对象,它对应python程序中初始的控制线程,但并不是daemon线程。

还存在一种“虚拟线程对象”被创建的可能。这些线程对象与“外来线程”对应,属于在线程模块外启动的控制线程,例如从C代码中产生的线程。虚拟线程对象的功能有限,通常被看做alive状态和daemon线程,且不能被join()。这些线程永远不会被删除,因为外来线程已经超出了python程序的管理范围。

类名称

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
该constructor通常应该通过关键词参数调用

类关键词

  • group:应该为None,为未来扩展作保留
  • target:是一个可调用的对象,可以通过run()函数调用,默认为None,意思没有被调用的东西。
  • name:线程名,默认使用“Thread-N”这种独特的名字进行构建,其中N是一个小的十进制数
  • args:一个目标调用的元祖,默认为()
  • kwargs:目标调用关键词参数的一个字典,默认为{}

如果子类要覆盖constructor,必须要确保在做任何其他工作前基础constructor(Thread.init())被调用。

类方法

  • start():启动一个线程活动,每个线程最多必须调用一次,它为对象的run()方法在分离的控制线程中被调用做准备
  • run():代表线程活动的方法,可以在子类中重写该方法。标准的run()方法调用被传递到constructor、作为target参数的可调用对象,如果有的话,顺序参数和关键字参数分别取自args和kwargs
  • join([timeout]):当代直到线程终止。它将阻塞调用线程直到带join的线程结束(包括正常终止/未处理异常/到时事件出现)。当timeout参数并没有给定或者是None时,该操作会阻塞直到线程终止。一个线程可以被join()ed多次。如果加入一个当前的线程会导致死锁,那么join()方法会报RuntimeError;此外,在线程还未启动时就调用join方法也会出现同样的报错。
  • name:一串用于确认的字符串,没有语义信息,在多线程中可能会被基于同样的name,其出事name由constructor设定。
  • ident:表示该线程的线程标志器,如果线程没有启动则为None。是一个非零整数,当一个线程存在并且另一个线程被创建时,线程标志器会被回收。标志器即使线程退出后也仍然可以使用。
  • is_alive()/isAlive():返回线程是否是alive状态
  • daemon:一个布尔值,表明该线程是否是daemon线程(True是False不是),该属性必须在start()方法前设置,否则会出现RuntimeError,它的初始值继承于线程创建;主线程并不是daemon线程,因此所有在主线程中被创建的线程的daemon属性都会被设置为False
  • isDaemon()/setDaemon():详见daemon模组

例子

一个threading.Tread代码例子

import threading, zipfile

class AsyncZip(threading.Thread):
    def __init__(self, infile, outfile):
        threading.Thread.__init__(self)
        self.infile = infile
        self.outfile = outfile

    def run(self):
        f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
        f.write(self.infile)
        f.close()
        print('Finished background zip of:', self.infile)

background = AsyncZip('mydata.txt', 'myarchive.zip')
background.start()
print('The main program continues to run in foreground.')

background.join()    # Wait for the background task to finish
print('Main program waited until background was done.')

16.2 多线程(multiprocessing):基于进程的线程交互

multiprocessing是一个支持类似threading模块包,它支持使用API来产生进程。multiprocessing提供本地和远程并发,通过使用子进程而不是线程的方法高效地绕过了全局解释器锁。因此,multiprocessing模块允许程序员全面利用机器的多核高效开展工作。multiprocessing支持在Unix和Windows上运行。

multiprocessing模块还引进了threading模块中没有的API。下面有一个简单的例子,Pool对象,它提供了一个接收多输入值函数并列执行的便捷方法,它将输入数据分发到各个进程中(数据并行)。下面的例子展示了上面提到的内容:

from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    p = Pool(5)
    print(p.map(f, [1, 2, 3]))

将会得到如下输出:

[1, 4, 9]

16.2.1 多线程对象:multiprocessing.Process

在multiprocessing中,进程通过调用一个Process对象并调用start()方法产生。Process遵循threading.Thread的API。一个multiprocessing程序的简单样例如下:

from multiprocessing import Process                         # import Process模块

def f(name):                                                                            # 功能函数定义
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))                          # 创建Process对象(其中target是f函数,f函数接收的参数是'bob')并执行初始化
    p.start()                                                                                # 启动进程
    p.join()																			        # 加入一个子进程

为了展示上面包括的各个进程的IDs,下面是一个扩展样例:

from multiprocessing import Process
import os

def info(title):
    print title
    print 'module name:', __name__
    if hasattr(os, 'getppid'):  # only available on Unix
        print 'parent process:', os.getppid()
    print 'process id:', os.getpid()

def f(name):
    info('function f')
    print 'hello', name

if __name__ == '__main__':
    info('main line')
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

类名称

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={})
Process对象代表了运行在分离进程中的活动,Process类拥有threading.Thread类相同的所有方法。constructor仍然需要带关键字调用。

类关键词

  • group:总默认为None,它存在的意义只是为了兼容threading.Thread
  • target:是可调用对象,被run()方法调用,默认为None
  • name:进程名,形如‘Process-N1:N2:…:Nk’
  • args:target调用中使用的参数
  • kwargs:target调用中关键字参数的一个字典

类方法

  • start():启动一个进程活动,每个进程对象最多必须调用一次,它为run()方法在分离的进程中被调用做准备
  • run():代表进程活动,可以在子类中重写该方法。标准的run()方法调用被传递到constructor、作为target参数的可调用对象(例子中的函数f),如果有的话,顺序参数和关键字参数分别取自args和kwargs
  • join([timeout]):直到线程终止。它将阻塞调用线程直到带join的线程结束(包括正常终止/未处理异常/到时事件出现)。当timeout参数并没有给定或者是None时,该操作会阻塞直到线程终止。一个线程可以被join()ed多次。如果加入一个当前的线程会导致死锁,那么join()方法会报RuntimeError;此外,在线程还未启动时就调用join方法也会出现同样的报错。
  • name:一串用于确认的字符串,没有语义信息,在多线程中可能会被基于同样的name,其出事name由constructor设定。
  • is_alive()/isAlive():返回线程是否是alive状态
  • daemon:一个布尔值,表明该线程是否是daemon线程(True是False不是),该属性必须在start()方法前设置,否则会出现RuntimeError,它的初始值继承于线程创建;主线程并不是daemon线程,因此所有在主线程中被创建的线程的daemon属性都会被设置为False
    下面是Process中新加入、threading.Thread中没有的属性:
  • pid:返回进程ID,进程产生前该值为None
  • exitcode:子退出代码,如果进程没有终止,该值为None。负值-N表示子进程N终止
  • authkey:进程的认证密钥,当multiprocessing被初始化后,主进程将被赋予一个随机的字符串。当Process对象被创建时,它将从它的父进程中继承认证密钥,但可以通过设置authkey属性来改变。
  • terminate():终止进程。在Unix系统中通过使用SIGTERM信号来终止。不会执行退出处理程序和 finally 子句等,进程的后代进程也不会被终止——只会成为孤立的进程。

start(), join(), is_alive(), terminate(), exitcode方法应该只在进程创建后调用

例子

下面是一个调用Process,

>>> import multiprocessing, time, signal
>>> p = multiprocessing.Process(target=time.sleep, args=(1000,))
>>> print p, p.is_alive()
<Process(Process-1, initial)> False
>>> p.start()
>>> print p, p.is_alive()
<Process(Process-1, started)> True
>>> p.terminate()
>>> time.sleep(0.1)
>>> print p, p.is_alive()
<Process(Process-1, stopped[SIGTERM])> False
>>> p.exitcode == -signal.SIGTERM
True

上面,很完美的一个例子,懂的都懂

16.3 不可重入的线程锁(Lock)

原语锁是一种原始的同步方法,当它上锁后并不会某一线程专有,其他线程也可以进行解锁。

原语锁只有两种状态:locked(锁)和unlocked(非锁)。创建原语锁时是处于unlocked状态。

原语锁类有两个方法:acquire()和realease()。当调用acquire时,该方法对整个进程(?应该是进程)进行上锁,直到release方法被其他线程调用(疑问:调用锁的线程不可以调用release吗?可以调用)

16.3.1 线程原语锁对象:threading.Lock

类名称

class threading.Lock
没有初始化参数

类关键字

None

类方法

  • acqure():上锁,阻塞后返回True。该方法有一个blocking参数,默认为True,默认阻塞进程,也可以设置为不阻塞,具体见官网方法说明
  • release():释放锁,任何进程都可以调用该方法(包括上锁进程本身),无返回值
  • locked():返回锁是否存在,如果锁存在则返回True

例子

None

16.4 可重入的线程锁(RLock)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值