Python基础之多线程总结(收藏这一篇就够了~)

Python基础之多线程

python提供了两个模块来实现多线程 thread 和 threading ;我们直接学习 threading 模块就可以了,因为thread模块功能不完整,所以不用浪费时间学习,只学习threading模块就 ok 啦~

1 多线程的创建

threading 模块多线程的创建方法有两种:1. 通过类的继承; 2. 使用 threading.Thread 创建;

1.1 通过继承创建多线程

这种方式的步骤为:

  1. 创建一个类,继承父类threading.Thread
  2. 重写类的初始化 init() 函数
  3. 重写类的run() 函数
  4. 创建一个新线程对象
  5. 启动新线程对象
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Zhang Kai time:2020/10/27

import threading
import time


class myThread(threading.Thread):  # 1. 创建一个类,继承父类threading.Thread
    def __init__(self, threadID, name, counter):    # 2. 重写类的初始化 __init__() 函数
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):  # 3. 重写类的run() 函数
        print("开始 " + self.name)
        print_time(self.name, self.counter, 3)    # 调用函数
        print("退出 " + self.name)


def print_time(threadName, delay, counter):
    while counter:
        print(threadName, '; Counter=', counter)
        if counter == 2 and threadName == "线程 2":  # 设置提前退出的条件
            break  # 通过设置break 可以提前结束线程
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))  # 打印当前时间
        print('当前线程为:', threading.currentThread(), '; 当前共有线程:', threading.activeCount(), '; 分别为:',
              threading.enumerate(), )
        counter -= 1


# 4. 创建一个新线程对象
thread1 = myThread(1, "线程 1", 1)
thread2 = myThread(2, "线程 2", 2)

# 5. 启动新线程对象
thread1.start()  # 开启线程 1
thread2.start()  # 开启线程 2
while True:
    if threading.activeCount() == 1:  # 当线程 1 & 线程 2 都退出时,只有一个主线程
        print("退出主线程")
        break

输出效果为:

开始 线程 1
线程 1 ; Counter= 3
开始 线程 2
线程 2 ; Counter= 3
线程 1: Tue Oct 27 15:56:29 2020
当前线程为: <myThread(线程 1, started 6288)> ; 当前共有线程: 3 ; 分别为: [<_MainThread(MainThread, started 39312)>, <myThread(线程 1, started 6288)>, <myThread(线程 2, started 40420)>]
线程 1 ; Counter= 2
线程 1: Tue Oct 27 15:56:30 2020
线程 2: Tue Oct 27 15:56:30 2020
当前线程为: <myThread(线程 2, started 40420)> ; 当前共有线程: 3 ; 分别为: [<_MainThread(MainThread, started 39312)>, <myThread(线程 1, started 6288)>, <myThread(线程 2, started 40420)>]
当前线程为: <myThread(线程 1, started 6288)> ; 当前共有线程: 3 ; 分别为: [<_MainThread(MainThread, started 39312)>, <myThread(线程 1, started 6288)>, <myThread(线程 2, started 40420)>]
线程 2 ; Counter= 2
线程 1 ; Counter= 1
退出 线程 2
线程 1: Tue Oct 27 15:56:31 2020
当前线程为: <myThread(线程 1, started 6288)> ; 当前共有线程: 2 ; 分别为: [<_MainThread(MainThread, started 39312)>, <myThread(线程 1, started 6288)>]
退出 线程 1
退出主线程

1.2 使用 threading.Thread 创建

这种方式的步骤为:

  1. 定义多线程需要调用的函数;
  2. 创建一个多线程对象;
  3. 启动多线程对象;
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Zhang Kai time:2020/10/27

import threading
import time


def print_time(threadName, delay, counter):    # 1. 定义多线程需要调用的函数;
    while counter:
        print(threadName, '; Counter=', counter)
        if counter == 2 and threadName == "线程 2":  # 设置提前退出的条件
            break  # 通过设置break 可以提前结束线程
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))  # 打印当前时间
        print('当前线程为:', threading.currentThread(), '; 当前共有线程:', threading.activeCount(), '; 分别为:',
              threading.enumerate(), )
        counter -= 1


th1 = threading.Thread(target=print_time, args=('线程 1',1,3))    # 2. 创建一个多线程对象;
th2 = threading.Thread(target=print_time, args=('线程 2',2,3))

th1.start()    # 3. 启动多线程对象;
th2.start()

输出效果为:

线程 1 ; Counter= 3
线程 2 ; Counter= 3
线程 1: Tue Oct 27 17:36:53 2020
当前线程为: <Thread(Thread-1, started 40524)> ; 当前共有线程: 3 ; 分别为: [<_MainThread(MainThread, stopped 41416)>, <Thread(Thread-1, started 40524)>, <Thread(Thread-2, started 40924)>]
线程 1 ; Counter= 2
线程 2: Tue Oct 27 17:36:54 2020
当前线程为: <Thread(Thread-2, started 40924)> ; 当前共有线程: 3 ; 分别为: [<_MainThread(MainThread, stopped 41416)>, <Thread(Thread-1, started 40524)>, <Thread(Thread-2, started 40924)>]
线程 2 ; Counter= 2
线程 1: Tue Oct 27 17:36:54 2020
当前线程为: <Thread(Thread-1, started 40524)> ; 当前共有线程: 2 ; 分别为: [<_MainThread(MainThread, stopped 41416)>, <Thread(Thread-1, started 40524)>]
线程 1 ; Counter= 1
线程 1: Tue Oct 27 17:36:55 2020
当前线程为: <Thread(Thread-1, started 40524)> ; 当前共有线程: 2 ; 分别为: [<_MainThread(MainThread, stopped 41416)>, <Thread(Thread-1, started 40524)>]

1.3 对比两种创建方法

从上面的例子可以看出,使用方法一(通过类的继承)的话,定义多线程更自由;重构threading.Thread类中的run方法,可以实现在调用函数的前后增加一些功能, 其次,还可以更改类中的线程名称,显然方法一更厉害一些~~

2. threading模块的方法

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常,或者是可选的超时时间。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。
  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

2.1 线程阻塞: join()的使用

在前面的例子中,我们使用while True:的判断来控制最后的输出,其实我们可以使用 join() 函数,join() 函数的意思就是使得主调线程阻塞,直到被调用线程运行结束或超时,才执行主线程。;举例如下:

import threading
import time


def print_time(threadName, delay, counter):
    while counter:
        print(threadName, '; Counter=', counter)
        if counter == 2 and threadName == "线程 2":  # 设置提前退出的条件
            break  # 通过设置break 可以提前结束线程
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))  # 打印当前时间
        counter -= 1


th1 = threading.Thread(target=print_time, args=('线程 1',1,3))
th2 = threading.Thread(target=print_time, args=('线程 2',2,3))

th1.start()
th2.start()

th2.join()
print("th2结束啦~")
th1.join()
print("退出主线程")

从输出结果可以看到,主程序先等待 th2 运行结束,再等待 th1 运行结束,然后才输出 “退出主线程”。输出效果为:

线程 1 ; Counter= 3
线程 2 ; Counter= 3
线程 1: Tue Oct 27 18:05:27 2020
线程 1 ; Counter= 2
线程 2: Tue Oct 27 18:05:28 2020
线程 2 ; Counter= 2
th2结束啦~
线程 1: Tue Oct 27 18:05:28 2020
线程 1 ; Counter= 1
线程 1: Tue Oct 27 18:05:29 2020
退出主线程

2.2 守护线程:setDaemon(True)的使用

setDaemon(True):把子线程设置为守护线程,主线程和子线程会同时运行,主线程结束运行后,无论子线程运行与否,都会和主线程一起结束。举例如下:

import threading
import time

def run(n):
    time.sleep(1)
    print("run the thread:",n)


num = 0
th1 = threading.Thread(target=run, args=(1,))
th1.setDaemon(True)
th1.start()

当这里使用了th1.setDaemon(True),则子线程被守护,会与主线程一起开始,一起结束;代码中,启动子线程后,主线程就结束了,这时的子线程还在time.sleep(1),所以来不及打印任何东西,子线程就和主线程一起结束了;

3. 全局变量的共享

import threading
import time


def work():
    global n
    print(threading.currentThread(), n)
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    print(threading.currentThread(),n)
    lock.release()


lock=threading.Lock()
n=100
l=[]
for i in range(5):
    p=threading.Thread(target=work)
    l.append(p)
    p.start()

for p in l:
    p.join()

输出效果:

<Thread(Thread-1, started 32472)> 100
<Thread(Thread-2, started 24768)> 100
<Thread(Thread-3, started 20584)> 100
<Thread(Thread-4, started 33452)> 100
<Thread(Thread-5, started 38496)> 100
<Thread(Thread-1, started 32472)> 99
<Thread(Thread-2, started 24768)> 98
<Thread(Thread-3, started 20584)> 97
<Thread(Thread-4, started 33452)> 96
<Thread(Thread-5, started 38496)> 95

4. 互斥锁

由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。为了方式上面情况的发生,就出现了互斥锁(Lock) ;使用了线程锁,则同一时刻只允许一个线程执行操作。打个比方:需要访问的变量就好像被所在了屋子里,当线程A拿到钥匙后(acquire()),A就可以对变量进行修改;这时线程B无法对变量进行修改,直到线程A还回钥匙(release())后,B 拿到钥匙(acquire()),B才可以对变量进行操作;

定义互斥锁的步骤:

  1. 定义一个锁对象:锁对象=threading.Lock()
  2. 请求锁:锁对象.acquire()
  3. 释放锁:锁对象.release()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author:Zhang Kai time:2020/10/27

import threading
import time

def work():
    global n
    print(threading.currentThread(), n)
    lock.acquire()    # 请求锁:锁对象.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    print(threading.currentThread(),n)
    lock.release()    # 释放锁:锁对象.release()


if __name__ == '__main__':
    lock=threading.Lock()    # 定义一个锁对象
    n=100
    l=[]
    for i in range(5):
        p=threading.Thread(target=work)
        l.append(p)
        p.start()

    for p in l:
        p.join()

输出效果:

<Thread(Thread-1, started 21036)> 100
<Thread(Thread-2, started 40296)> 100
<Thread(Thread-3, started 14972)> 100
<Thread(Thread-4, started 8360)> 100
<Thread(Thread-5, started 26640)> 100
<Thread(Thread-1, started 21036)> 99
<Thread(Thread-1, started 21036)> 99
<Thread(Thread-2, started 40296)> 98
<Thread(Thread-2, started 40296)> 98
<Thread(Thread-3, started 14972)> 97
<Thread(Thread-3, started 14972)> 97
<Thread(Thread-4, started 8360)> 96
<Thread(Thread-4, started 8360)> 96
<Thread(Thread-5, started 26640)> 95
<Thread(Thread-5, started 26640)> 95

5. 递归锁

锁本质上是阻止其他线程进入,当只有一个需要阻止其他进程进入的操作,只需要互斥锁; 但有时候,我们可能有两个,甚至更多的需要阻止其他线程进入的操作,当然我们可以通过定义很多个互斥锁来完成这个工作,但为了方便起见,我们定义了递归锁。递归锁就是为了处理这种情况,递归锁对象允许多次acquire和多次release。递归锁(RLock())更像是深度上锁;

递归锁本质上还是一个锁,但如果在一个线程里面可以多次acquire。【因为只有一个锁,所以不会发生互相调用的死锁,而因为可以多次调用,所以可以锁多次】

定义递归锁的步骤:

  1. 定义一个锁对象:递归锁对象=threading.RLock()
  2. 请求锁:锁对象.acquire()
  3. 释放锁:锁对象.release()
import threading
import time

# 递归锁
def rlock():
    print('我是F1,准备开始咯~')
    printLock.acquire()
    time.sleep(2)
    print('我是F1,我进来了第一层锁')
    printLock.acquire()
    time.sleep(2)
    print('我是F1,我进来了第二层锁')
    time.sleep(2)
    printLock.release()
    print('我是F1,我解开了第二层锁')
    time.sleep(2)
    printLock.release()
    print('我是F1,我解开了第一层锁')
    print('我是F1,我自由啦~~')


def rlock2():
    print('我是F2,准备开始咯~')
    printLock.acquire()
    time.sleep(2)
    print('我是F2,我进来了第一层锁')
    printLock.acquire()
    time.sleep(2)
    print('我是F2,我进来了第二层锁')
    time.sleep(2)
    printLock.release()
    print('我是F2,我解开了第二层锁')
    time.sleep(2)
    printLock.release()
    print('我是F2,我解开了第一层锁')
    print('我是F2,我自由啦~~')


printLock = threading.RLock()

th1 = threading.Thread(target=rlock)
th2 = threading.Thread(target=rlock2)

th1.start()
th2.start()

th1.join()
th2.join()

输出效果:

我是F1,准备开始咯~
我是F2,准备开始咯~
我是F1,我进来了第一层锁
我是F1,我进来了第二层锁
我是F1,我解开了第二层锁
我是F1,我解开了第一层锁
我是F1,我自由啦~~
我是F2,我进来了第一层锁
我是F2,我进来了第二层锁
我是F2,我解开了第二层锁
我是F2,我解开了第一层锁
我是F2,我自由啦~~

从输出结果我们可以看到,两个线程井井有条,F2准备开始后,但一直无法进入,因为权限在F1的手中,直到F1把锁的权限release后,F2才能开始运行;这就是锁的作用;递归锁就是可以锁很多次~ 这样说应该没有问题吧~~

6. 信号量

上面讲的锁,同一时间只允许一个线程访问,如果允许规定数量的线程可以访问,则需要使用信号量(BoundedSemaphore类),比如:semaphore = threading.BoundedSemaphore(5),就可以允许5个线程同时运行;

使用信号量的步骤:

  1. 创建信号量对象:信号量对象=threading.BoundedSemaphore(x),x是限制进程的数量
  2. 当有进程需要进入的时候,调用acquire()来减少信号量:信号量对象.acquire()
  3. 当有进程离开的时候,调用release()来增加信号量:信号量对象.release()
import threading
import time
import random

def run(n, semaphore):
    semaphore.acquire()   #加锁
    print("run the thread:%s" % n)
    time.sleep(random.randint(2,5))   # 模拟不同线程的执行时间,有长有短;
    print(n, '释放了')
    semaphore.release()     #释放


if __name__ == '__main__':
    num = 0
    semaphore = threading.BoundedSemaphore(3)  # 最多允许5个线程同时运行
    for i in range(10):
        t = threading.Thread(target=run, args=("t-%s" % i, semaphore))
        t.start()
    while threading.active_count() != 1:
        pass  # print threading.active_count()
    else:
        print('-----all threads done-----')

输出效果:同一时间只能有三个线程,当有线程被释放时,才能有新的线程进入,输出如下:

run the thread:t-0
run the thread:t-1
run the thread:t-2
t-0 释放了
run the thread:t-3
t-2 释放了
run the thread:t-4
t-1 释放了
run the thread:t-5
t-5 释放了
run the thread:t-6
t-3 释放了
run the thread:t-7
t-4 释放了
run the thread:t-8
t-6 释放了
run the thread:t-9
t-7 释放了
t-9 释放了
t-8 释放了
-----all threads done-----

7. 事件

当发生线程发生一件事的时候如果要提醒另外一个线程,使用事件。例如:绿灯亮起,我们就提醒车辆可以行驶了;红灯亮起,我们就提醒车辆要停下来;

事件的创建步骤为:

  1. 如何使用事件:
  2. 创建事件对象:事件对象=threading.Event()
  3. 设置事件:事件对象.set() 判断事件是否set:事件对象.is_set(),等待事件set:事件对象.wait()
  4. 清除事件:事件对象.clear()
import threading
import time

event = threading.Event()


def lighter():
    count = 0
    event.set()     # 初始化时间,默认为绿灯
    while True:
        if 3 < count <=6 :
            event.clear()  # 时间清除,意为红灯亮起
            print("\33[41;1m红灯亮起...\033[0m")
        elif count > 6:
            count = 0
        else:
            print("\33[46;32m绿灯亮起\033[0m")
            event.set()  # 绿灯,设置标志位
        time.sleep(1)
        count += 1


def car(name):
    while True:
        if event.is_set():      #判断是否设置了标志位
            print("[%s] 绿灯亮了,走喽~~"%name)
            time.sleep(1)
        else:
            print("[%s] 看着红灯静静等待"%name)
            event.wait()
            # print("[%s] 绿灯亮了,走喽~~"%name)

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

car = threading.Thread(target=car,args=("我的小车车",))
car.start()

print 的显示规则,参考:https://blog.csdn.net/wo1769815/article/details/104108850

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这么神奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值