Python学习:threading模块

介绍

多任务可以由多进程完成,也可以由一个进程内的多个线程完成,一般进程是由若干个线程组成的,一个进程至少包括一个线程。

由于线程是由操作系统直接支持的执行单元,因此高级语言通常都内置多线程支持,Python的threading模块是多线程支持的实现模块。另外还有个_thread模块也是支持多线程,但是是低级模块,而threading模块是对_thread模块的封装,因此一般使用threading这个高级模块即可。

方法与属性

threading模块提供了一些实用的方法与属性,主要包括:

序号方法或属性描述
1current_thread()返回当前的线程对象
2active_count()返回当前活跃的线程数,包括一个主线程与N个子线程
3get_ident()返回当前线程的thread identifier,是个非零整数
4enumerater()返回当前存活的线程对象列表
5main_thread()返回主thread对象
6settrace(func)为所有的线程设置一个trace函数
7setprofile(func)为所有的线程设置一个profile函数
8stack_size([size])返回新创建线程的栈大小,或者为后续创建的线程设定栈大小为size
9TIMEOUT_MAXLock.acquire()、RLock.acquire()、Condition.wait()允许的最大超时时间

包含的类

threading模块包含以下的类:

  1. Thread,基本线程类;
  2. Lock,互斥锁;
  3. RLock,可重入锁,使得单一进程再次获得已持有的锁;
  4. Condition,条件锁,使得一个线程等待另一个线程满足特定的条件;
  5. Semaphore,信号锁,为线程之间共享的有限资源提供计数器;
  6. Event,事件锁,任意数量的线程等待某个事件发生,发生后所有等待的线程将被激活;
  7. Timer,计时器;
  8. Barrier,栅栏,必须达到指定数量的线程后,才能从阻塞中恢复。

Thread类

创建多线程的方法有两种。

一种是继承Thread类,并重写run方法。另一种是实例化Thread对象时,将线程要执行的任务函数作为参数传入线程。

代码范例:

#继承Thread类,并重写run方法
import threading

class MyThread(threading.Thread):
    def __init__(self, thread_name):
        super(MyThread, self).__init__(name = thread_name)

    # 重写run()方法
    def run(self):
        print("%s正在运行中......" % self.name)

for i in range(10):
    MyThread("thread-" + str(i)).start()   # 启动线程

#实例化Thread对象时,将线程要执行的任务函数作为参数传入线程 
import threading
import time

def show(arg):
    time.sleep(1)
    print("thread " + str(arg) + " running......")

for i in range(10):
    t = threading.Thread(target=show, args=(i,))  # 注意传入的参数一定是一个元组!
    t.start()

Thread类的定义如下:

threading.Thread(self, group=None, target=None, name=None,agrs=(),kwargs=None, *, daemon=None)
#参数group预留,用于将来的扩展
#参数target是一个可调用的对象,在线程启动后可执行,即线程入口
#参数name是线程的名字,默认线程名字为“Thread-N”
#参数args是调用target时的参数列表
#参数kwargs是调用target时的关键字参数

Thread类的方法与属性

Thread类定义的常用方法与属性如下:

序号方法或属性描述
1start()启动线程,等待CPU的调度
2run()线程被CPU调度后自行执行的方法,若继承Thread类,该方法可被重载
3getName()、setName()、name用于获取和设置线程的名称
4setDaemon()设置为后台线程或者前台线程(默认为前台线程),如果是后台线程,在主线程执行过程中,后台线程也在执行。主线程执行完成后,后台线程无论是否成功,均停止。如果是前台线程,主线程执行过程中,前台线程也在执行,主线程执行完成后,等待前台线程执行完成,程序才停止。
5ident获取线程的标识符,为一个非零整数,在调用start方法后,该属性才生效。
6is_alive()判断线程是否是激活的,从调用start方法启动线程开始,到run方法执行完成,或者执行过程中遇到异常中断为止的这段时间,线程是激活的状态
7isDaemon()、daemon判断是否为守护进程
8join([timeout])调用该方法,则主调线程将阻塞,直到被调用线程运行结束或者超时,如果timeout未指定,则主调线程将一直阻塞到被调线程的结束。

在多线程的执行过程中,各个线程,包括主线程,是各自执行自己的任务,若没有调用特殊的方法,则各个线程不会等待别的线程的结束。比如:

import time
import threading

def doWaiting():
    print("开始等待:", time.strftime('%H:%M:%S'))
    time.sleep(3)
    print("结束等待:", time.strftime("%H:%M:%S"))

t = threading.Thread(target=doWaiting)
t.start()

time.sleep(1)  # 确保线程已经启动
print("开始工作")
print("结束工作")

通过调用join方法,主线程可以等待子线程的结束,如下:

import threading
import time

def doWaiting():
    print("开始等待: ", time.strftime("%H:%M:%S"))
    time.sleep(3)
    print("结束等待:", time.strftime("%H:%M:%S"))

t = threading.Thread(target=doWaiting)

t.start()
# 确保线程t已经启动
time.sleep(1)

print("开始阻塞主线程,等待子线程执行")
t.join()   # 主线程不要着急走,等等子线程吧!!! 将一直堵塞,直到t运行结束
print("子线程执行完,结束阻塞,主线程继续执行!")

还可以使用setDaemon(True)吧所有的子线程都变成主线程的守护进程。当主线程结束后,守护子进程也会随之结束,整个程序也跟着退出。

import threading
import time

def run():
    print(threading.current_thread().getName(), "开始工作")
    time.sleep(2)  # 子线程停两秒
    print("子线程工作执行完成!")

for i in range(3):
    t = threading.Thread(target=run)
    t.setDaemon(True)   # 把子线程设置为守护进程,必须在start()之前设置!!!
    
    t.start()
    
time.sleep(1)  # 主线程停1s
print("主线程结束运行...")
print(threading.active_count())  # 输出活跃的线程数量

自定义线程类

对于threading模块的Thread类,本质上是执行了它的run()方法。因此可以字定义线程类,让它继承Thread类,然后重新run()方法即可。

import threading

class MyThreading(threading.Thread):
    def __init__(self, func, arg):
        super(MyThreading, self).__init__()
        self.func = func
        self.arg = arg
    # 重写run()方法
    def run(self):
        self.func(self.arg)


def my_func(args):
    '''
    此处可以把你想让线程做的事定义在这里
    '''
    print("我是业务函数...")
    pass
obj = MyThreading(my_func, 123)
obj.start()

线程锁

由于线程之间的任务执行是CPU进行随机调度的,并且每个线程可能只执行了n条指令之后就被切换到别的线程了。当多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,这被称为“线程不安全”。为了保证数据安全,我们设计了线程锁,即同一时刻只允许一个线程操作该数据。线程锁用于锁定资源,可以同时使用多个锁,当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个箱子锁住是一个道理。

互斥锁

互斥锁是一种独占锁,同一时刻只有一个线程可以访问共享的数据。使用很简单,初始化锁对象,然后将锁当做参数传递给任务函数,在任务中加锁,使用后释放锁。

import threading
import time

number = 0

lock = threading.Lock()  # 锁对象!

def plus(lk):
    global number        # global声明此处的number是外面的全局变量number
    lk.acquire()  # 开始加锁!!!
    for _ in range(1000000):   # 进行一个大数级别的循环加一运算
        number += 1
    print("子线程%s运算结束后,number = %s" % (threading.current_thread().getName(), number))
    lk.release()   # 释放锁,让别的线程也可以访问number!!!
    
    
for i in range(2):   # 用2个子线程,就可以观察到脏数据
    t = threading.Thread(target=plus, args=(lock,))
    t.start()

time.sleep(3)    # 等待3秒,确保2个子线程都已经结束运算
print("主线程执行完成后,number = ", number)

RLock的使用方法和Lock一模一样,只不过它支持重入锁。该锁对象内部维护着一个Lock和一个counter对象。counter对象记录了acquire的次数,使得资源可以被多次require。最后,当所有RLock被release后,其他线程才能获取资源。在同一个线程中,RLock.acquire()可以被多次调用,利用该特性,可以解决部分死锁问题。

信号量

类名:BoundedSemaphore。这种锁允许一定数量的线程同时更改数据,它不是互斥锁。比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。

import time
import threading

def run(n, se):
    se.acquire()
    print("run the thread: %s" % n)
    time.sleep(1)
    se.release()

# 设置5个线程允许同时运行
semaphore = threading.BoundedSemaphore(5)
for i in range(20):
    t = threading.Thread(target=run, args=(i, semaphore))
    t.start()

事件

类名Event, 事件线程锁的运行机制:全局定义了一个Flag,如果Flag的值为False,那么当程序执行wait()方法时就会阻塞,如果Flag值为True,线程不再阻塞。这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有排队中的线程。事件主要提供了四个方法set()、wait()、clear()和is_set()。

  1. clear()方法会将事件的Flag设置为False
  2. set()方法会将Flag设置为True
  3. wait()方法将等待“红绿灯”信号
  4. is_set():判断当前是否"绿灯放行"状态

下面是一个模拟红绿灯,然后汽车通行的例子:

import threading
import time

event = threading.Event()

def lighter():
    green_time = 5  # 绿灯时间
    red_time = 5   # 红灯时间
    event.set()   # 初始设为绿灯
    while True:
        print("绿灯亮...")
        time.sleep(green_time)
        event.clear()
        print("红灯亮...")
        time.sleep(red_time)
        event.set()

def run(name):
    while True:
        if event.is_set():    # 判断当前是否"放行"状态
            print("一辆[%s] 呼啸开过..." % name)
            time.sleep(1)
        else:
            print("一辆[%s]开来,看到红灯,无奈的停下了..." % name)
            event.wait()
            print("[%s] 看到绿灯亮了,瞬间飞起....." % name)

lighter = threading.Thread(target=lighter,)
lighter.start()

for name in ['奔驰', '宝马', '奥迪']:
    car = threading.Thread(target=run, args=(name,))
    car.start()

条件

类名:Condition。Condition称作条件锁,依然是通过acquire()/release()加锁解锁。

wait([timeout])方法将使线程进入Condition的等待池等待通知,并释放锁。使用前线程必须已获得锁定,否则将抛出异常。

notify()方法将从等待池挑选一个线程并通知,收到通知的线程将自动调用acquire()尝试获得锁定(进入锁定池),其他线程仍然在等待池中。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

notifyAll()方法将通知等待池中所有的线程,这些线程都将进入锁定池尝试获得锁定。调用这个方法不会释放锁定。使用前线程必须已获得锁定,否则将抛出异常。

import threading
import time

num = 0
con = threading.Condition()

class Foo(threading.Thread):
    def __init__(self, name, action):
        super(Foo, self).__init__()
        self.name = name
        self.action = action

    def run(self):
        global num
        con.acquire()
        print("%s开始执行..." % self.name)
        while True:
            if self.action == "add":
                num += 1
            elif self.action == 'reduce':
                num -= 1
            else:
                exit(1)
            print("num当前为:", num)
            time.sleep(1)
            if num == 5 or num == 0:
                print("暂停执行%s!" % self.name)
                con.notify()
                con.wait()
                print("%s开始执行..." % self.name)
        con.release()

if __name__ == '__main__':
    a = Foo("线程A", 'add')
    b = Foo("线程B", 'reduce')
    a.start()
    b.start()

定时器

定时器Timer类是threading模块中的一个小工具,用于指定n秒后执行某操作。一个简单但很实用的东西。

from threading import Timer

def hello():
    print("hello world")

# 表示1s后执行hello函数
t = Timer(1, hello)
t.start()

通过with语句使用线程锁

所有的线程锁都有一个加锁和释放锁的动作,非常类似文件的打开和关闭。在加锁后,如果线程执行过程中出现异常或者错误,没有正常的释放锁,那么其他的线程会造到致命性的影响。通过with上下文管理器,可以确保锁被正常释放。

with some_lock:
   # 执行任务.... 

#相当于以下的代码
ome_lock.acquire()
try:
    # 执行任务..
finally:
    some_lock.release()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值