zoukankan      html  css  js  c++  java
  • python使用多线程

    threading 模块支持守护线程, 其工作方式是:守护线程一般是一个等待客户端请求服务的服务器。 

    如果把一个线程设置为守护线程,进程退出时不需要等待这个线程执行完成。

    如果主线程准备退出时,不需要等待某些子线程完成,就可以为这些子线程设置守护线程标记。 需要在启动线程之前执行如下赋值语句: thread.daemon = True,检查线程的守护状态也只需要检查这个值即可。

    整个 Python 程序将在所有非守护线程退出之后才退出, 换句话说, 就是没有剩下存活的非守护线程时才退出。 

    使用thread模块

    以下是三种使用 Thread 类的方法(一般使用第一个或第三个方案)

    • 创建 Thread 的实例,传给它一个函数。

    import threading
    from time import sleep, ctime
    loops = [3, 2, 1, 1, 1]
    def loop(i, nsec):
        print(f'start loop {i} at: {ctime()}')
        sleep(nsec)
        print(f'end loop {i} at: {ctime()}')
    def main():
        print('start 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: # start threads
            threads[i].start()
        for i in nloops: # wait for all
            threads[i].join() # threads to finish
        print(f'all done at: {ctime()}')
    if __name__ == '__main__':
        main()
    

    当所有线程都分配完成之后,通过调用每个线程的 start()方法让它们开始执行,而不是 在这之前就会执行。
    相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,这里只 需要为每个线程调用 join()方法即可。
    join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间。
    使用 join()方法要比等待锁释放的无限循环更加清晰(这也是这种锁 又称为自旋锁的原因)。

    • 创建 Thread 的实例,传给它一个可调用的类实例。

    import threading
    from time import sleep, ctime
    # 创建 Thread 的实例,传给它一个可调用的类实例
    loops = [3, 2, 1, 1, 1]
    class ThreadFunc(object):
        def __init__(self, func, args, name=''):
            self.name = name
            self.func = func
            self.args = args
        def __call__(self):
            self.func(*self.args)
    def loop(i, nsec):
        print(f'start loop {i} at: {ctime()}')
        sleep(nsec)
        print(f'end loop {i} at: {ctime()}')
    def main():
        print('start 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: # start threads
            threads[i].start()
        for i in nloops: # wait for all
            threads[i].join() # threads to finish
        print(f'all done at: {ctime()}')
    if __name__ == '__main__':
        main()
    • 派生 Thread 的子类,并创建子类的实例。

    import threading
    from time import sleep, ctime
    # 创建 Thread 的实例,传给它一个可调用的类实例
    # 子类的构造函数必须先调用其基类的构造函数
    # 特殊方法__call__()在 子类中必须要写为 run()
    loops = [3, 2, 1, 1, 1]
    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(i, nsec):
        print(f'start loop {i} at: {ctime()}')
        sleep(nsec)
        print(f'end loop {i} at: {ctime()}')
    def main():
        print('start 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: # start threads
            threads[i].start()
        for i in nloops: # wait for all
            threads[i].join() # threads to finish
        print(f'all done at: {ctime()}')
    if __name__ == '__main__':
        main()
    

    使用锁

    python和java一样,也具有锁机制,而且创建与使用锁都是很简便的。

    一般在多线程代码中,总会有一些特 定的函数或代码块不希望(或不应该)被多个线程同时执行,通常包括修改数据库、更新文件或 其他会产生竞态条件的类似情况

    锁有两种状态:锁定和未锁定。而且它也只支持两个函数:获得锁和释放锁。

    一般锁的调用如下

    # 加载线程的锁对象
    lock = threading.Lock()
    # 获取锁
    lock.acquire()
    # ...代码
    # 释放锁
    lock.release()
    

    更简洁的方法是使用with关键字,如下代码功能同上

    # 加载线程的锁对象
    lock = threading.Lock()
    with lock :
        #...代码
    

    示例代码:

    import threading
    from time import sleep, ctime
    
    lock = threading.Lock()
    
    def a():
        lock.acquire()
        for x in range(5):
            print(f'a:{str(x)}')
            sleep(0.01)
        lock.release()
    
    
    def b():
        lock.acquire()
        for x in range(5):
            print(f'a:{str(x)}')
            sleep(0.01)
        lock.release()
    
    
    threading.Thread(target=a).start()
    threading.Thread(target=b).start()
    

    相关属性和方法 

    • Thread对象的属性

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

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

    函数描述
    start() 开始执行该线程
    active_count() 当前活动的 Thread 对象个数
    enumerate() 返回当前活动的 Thread 对象列表
    settrace(func) 为所有线程设置一个 trace 函数
    setprofile (func) 为所有线程设置一个 profile 函数
    stack_size(size=0) 返回新创建线程的栈大小;或为后续创建的线程设定栈的大小 为 size
    Lock() 加载线程的锁对象,是一个基本的锁对象,一次只能一个锁定,其余锁请求,需等待锁释放后才能获取,对象有acquire()和release()方法
    RLock() 多重锁,在同一线程中可用被多次acquire。如果使用RLock,那么acquire和release必须成对出现,调用了n次acquire锁请求,则必须调用n次的release才能在线程中释放锁对象

    后记 

    在Python多线程下,每个线程的执行方式:
    1、获取GIL
    2、执行代码直到sleep或者是python虚拟机将其挂起。
    3、释放GIL

    通常来说,多线程是一个好东西。不过由于 Python 的 GIL 的限制,多线程更适合于 I/O 密集型应用(I/O 释放了 GIL,可以允 许更多的并发),而不是计算密集型应用。对于后一种情况而言,为了实现更好的并行性,你需要使用多进程,以便让 CPU 的其他内核来执行。

    请注意:多核多线程比单核多线程更差,原因是单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低

  • 相关阅读:
    一个apache安装后无法启动的原因分析
    数字的一点考虑
    [转]bat方式上删除注册表键,项
    题解 P2016 【战略游戏】
    题解 P1403 【[AHOI2005]约数研究】
    题解 P1317 【低洼地】
    2020面向对象程序设计寒假作业3 设计思想
    题解 P1829 【[国家集训队]Crash的数字表格 / JZPTAB】
    题解 P1082 【同余方程】
    Unity3D读取外部Text
  • 原文地址:https://www.cnblogs.com/chenqionghe/p/9668829.html
Copyright © 2011-2022 走看看