171 views
Python高级技巧

7.Python多任务-线程与锁

文章目录

什么是线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

Python中线程的问题

在Python3中实现的大部分运行任务里,不同的线程实际上并没有同时运行:"它们只是看起来像是同时运行的。"

1.大家很容易认为线程化是在程序上运行两个(或多个)不同的处理器,每个处理器同时执行一个独立的任务。
  这种理解并不完全正确,线程可能会在不同的处理器上运行,但一次只能运行一个线程。

2.同时执行多个任务需要使用非标准的Python运行方式:用不同的语言编写一部分代码,
  或者使用多进程模块multiprocessing,但这么做会带来一些额外的开销。

3.由于Python默认的运行环境是CPython(C语言开发的Python),所以线程化可能不会提升所有任务的运行速度。
  这是因为和GIL(Global Interpreter Lock)的交互形成了限制:一次只能运行一个Python线程。

4.线程化的一般替代方法是:
- 让各项任务花费大量时间等待外部事件。
- 但问题是,如果想缩短等待时间,会需要大量的CPU计算,结果是程序的运行速度可能并不会提升。

5.当代码是用Python语言编写并在默认执行环境CPython上运行时,会出现这种情况。
  如果线程代码是用C语言写的,那它们就能够释放GIL并同时运行。

6.如果只用Python语言在默认的Python执行环境下运行,并且遇到CPU受限的问题,
  那就应该用"多进程模块multiprocessing"来解决。

在程序中使用线程也可以简化设计。本文中的大部分示例并不保证可以提升程序运行速度,其目的是使设计结构更加清晰、便于逻辑推理。

threading线程常用方法

python标准库-threading

1.实例化thread类
t = threading.Thread(group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None)
参数解析:
group:线程组,貌似没什么用
target:调用的方法名,记得不加()
name:线程开启后,该线程的名字
args:参数,元组的形式传入
kwargs:参数
daemon:设置是否为守护线程

2.以列表形式返回当前所有存活的Thread对象
threading.enumerate()   

3.返回当前存活的线程类 Thread 对象。返回的计数等于 enumerate() 返回的列表长度。
threading.active_count()

4.阻塞函数,timeout 允许的最大值。传入超过这个值的 timeout 会抛出 OverflowError 异常。
threading.TIMEOUT_MAX

5.start()
开始线程活动。
6.run()
代表线程活动的方法。
7.join(timeout=None)
等待,直到所有线程终结。
8.is_alive()
返回线程是否存活。
9.daemon
一个表示这个线程是(True)否(False)守护线程的布尔值。
一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。
初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False。
当没有存活的非守护线程时,整个Python程序才会退出。

开启线程的两种方法

# Thread是线程类,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖run()
# 当调用Thread的时候,不会创建线程。
# 当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程。

一.直接传入要运行的方法
# coding:utf8
import threading
import time
def dog(num):
    print("汪 汪 汪 - %s" %num)
    time.sleep(1)


if __name__ == '__main__':
    # 1.普通模式启动
    t1_start=time.time()
    for num in range(5):
        dog(num)
    t2_end=time.time()
    print("普通模式耗时:%s" %(t2_end-t1_start))

    # 2.线程模式
    t1_start=time.time()
    for num in range(5):
        t1 = threading.Thread(target=dog,args=(num,))
        t1.start()
    t2_end=time.time()
    print("线程模式耗时:%s" %(t2_end-t1_start))
-------------------------结果--------------------------------------
汪 汪 汪 - 0
汪 汪 汪 - 1
汪 汪 汪 - 2
汪 汪 汪 - 3
汪 汪 汪 - 4
普通模式耗时:5.004029750823975
汪 汪 汪 - 0
汪 汪 汪 - 1
汪 汪 汪 - 2
汪 汪 汪 - 3
汪 汪 汪 - 4
线程模式耗时:0.003961086273193359
------------------------------------------------------------------

二.从Thread继承并覆盖run()
import threading
import time

class A(threading.Thread):

    def __init__(self):
        super().__init__()

    #这里一定是run方法,不能是其他的
    def run(self):
        for i in range(5):
            print(i,end='')

if __name__ == "__main__":
    t = A()    
    t.start()    
------------------------------------------------------------------
0 1 2 3 4

线程之间的通信-锁

如果多个线程对某一资源同时进行修改,可能会存在不可预知的情况。
为了修改数据的正确性,需要把这个资源锁住,只允许线程依次排队进去获取这个资源。
当线程A操作完后,释放锁,线程B才能进入。

创建锁
mutex = threading.Lock()
锁定
mutex.acquire()
解锁
mutex.release()

# coding:utf8
import threading,time
#创建锁
lock=threading.Lock()
num=0
def dog(nums):
    global num
    #加锁
    lock.acquire()
    for i in range(nums):
        num+=1
    #解锁    
    lock.release()
    print("汪 汪 汪 - %s " %num)
def cat(nums):
    global num
    lock.acquire()
    for i in range(nums):
        num+=1
    lock.release()
    print("miao miao miao - %s " % num)
if __name__ == '__main__':
    t1=threading.Thread(target=dog,args=(1000000,))
    t2=threading.Thread(target=cat,args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(2)
    print('main',num)
-------------结果-----------------
汪 汪 汪 - 1000000 
miao miao miao - 2000000 
main 2000000
注:如果不加锁,结果会不真实