Python多线程之threading、Lock、Semaphore、Condition、Event、queue、Pool

1. 使用threading模块操作多线程有以下两种方法:

方法一:创建threading.Thread类的实例,调用其start()方法

示例:

import time
import threading


def task_thread(counter):
    print(
        f'线程名称:{threading.current_thread().name} 参数:{counter} 开始时间:{time.strftime("%Y-%m-%d %H:%M:%S")}'
    )
    num = counter
    while num:
        time.sleep(3)
        num -= 1
    print(
        f'线程名称:{threading.current_thread().name} 参数:{counter} 结束时间:{time.strftime("%Y-%m-%d %H:%M:%S")}'
    )


if __name__ == "__main__":
    # 初始化3个线程,传递不同的参数
    t1 = threading.Thread(target=task_thread, args=(3,))
    t2 = threading.Thread(target=task_thread, args=(2,))
    t3 = threading.Thread(target=task_thread, args=(1,))
    # 开启三个线程
    t1.start()
    t2.start()
    t3.start()
    # 等待运行结束
    t1.join()
    t2.join()
    t3.join()

说明:程序实例化了三个Thread类的实例,并向任务函数传递不同的参数,使他们运行不同的时间后结束,start()方法开启线程,join()方法等待线程结束

输出:

线程名称:Thread-1 参数:3 开始时间:2021-03-04 15:36:39
线程名称:Thread-2 参数:2 开始时间:2021-03-04 15:36:39
线程名称:Thread-3 参数:1 开始时间:2021-03-04 15:36:39

线程名称:Thread-3 参数:1 结束时间:2021-03-04 15:36:42
线程名称:Thread-2 参数:2 结束时间:2021-03-04 15:36:45
线程名称:Thread-1 参数:3 结束时间:2021-03-04 15:36:48

方法二:继承Thread类,在子类中重写run()和init()方法

示例

import time
import threading


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

    def run(self):
        print(
            f'线程名称:{threading.current_thread().name} 参数:{self.counter} 开始时间:{time.strftime("%Y-%m-%d %H:%M:%S")}'
        )
        counter = self.counter
        while counter:
            time.sleep(3)
            counter -= 1
        print(
            f'线程名称:{threading.current_thread().name} 参数:{self.counter} 结束时间:{time.strftime("%Y-%m-%d %H:%M:%S")}'
        )


if __name__ == "__main__":

    # 初始化3个线程,传递不同的参数
    t1 = MyThread(3)
    t2 = MyThread(2)
    t3 = MyThread(1)
    # 开启三个线程
    t1.start()
    t2.start()
    t3.start()
    # 等待运行结束
    t1.join()
    t2.join()
    t3.join()

输出

线程名称:Thread-1 参数:3 开始时间:2021-03-04 15:40:41
线程名称:Thread-2 参数:2 开始时间:2021-03-04 15:40:41
线程名称:Thread-3 参数:1 开始时间:2021-03-04 15:40:41

线程名称:Thread-3 参数:1 结束时间:2021-03-04 15:40:44
线程名称:Thread-2 参数:2 结束时间:2021-03-04 15:40:47
线程名称:Thread-1 参数:3 结束时间:2021-03-04 15:40:50

2. 多进程同步之Lock锁

多个线程之间对某个数据进行修改,会出现不可预料的结果。

实例:不加锁的情况

import threading


def task_thread(n):
    global num
    for i in range(1000000):
        num = num + n
        num = num - n


if __name__ == '__main__':
    num = 0
    t1 = threading.Thread(target=task_thread, args=(6,))
    t2 = threading.Thread(target=task_thread, args=(17,))
    t3 = threading.Thread(target=task_thread, args=(11,))
    t1.start()
    t2.start()
    t3.start()
    t1.join()
    t2.join()
    t3.join()
    print(num)

输出(每次结果输出都不一样):

-15

说明:上述代码,实例化3个线程,去执行task_thread任务函数,num=0的概率是非常小的,因为当一个线程执行num + n 时,另一个线程可能正在执行num - m,导致之前的线程执行num - n时 num已经不是之前的值。这是由于线程抢占造成的,想要避免这种情况,就得给线程加锁。

实例:加锁的情况

import threading

lock = threading.Lock()


def task_thread(n):
    global num
    # 获取锁,用于线程同步
    lock.acquire()
    for i in range(1000000):
        num = num + n
        num = num - n
    # 释放锁,开启下一个线程
    lock.release()


if __name__ == '__main__':
    num = 0
    t1 = threading.Thread(target=task_thread, args=(6,))
    t2 = threading.Thread(target=task_thread, args=(17,))
    t3 = threading.Thread(target=task_thread, args=(11,))
    t1.start()
    t2.start()
    t3.start()
    t1.join()
    t2.join()
    t3.join()
    print(num)

输出:无论执行多少次,结果都是0

0

3. 多线程同步之Semaphore(信号量)

Lock锁只是允许一个线程访问共享数据,而信号量是同时允许一定数量的线程访问共享数据。

示例:

import threading
import time


# 模拟银行业务办理
def yewubanli(name):
    semaphore.acquire()
    time.sleep(3)
    print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {name} 正在办理业务")

    semaphore.release()


if __name__ == '__main__':
    
    # 同时只有5个人办理业务
    semaphore = threading.BoundedSemaphore(5)
    thread_list = []
    for i in range(12):
        t = threading.Thread(target=yewubanli, args=(i,))
        thread_list.append(t)
    
    for thread in thread_list:
        thread.start()

    for thread in thread_list:
        thread.join()

输出

2021-03-04 17:24:00 0 正在办理业务
2021-03-04 17:24:00 1 正在办理业务
2021-03-04 17:24:00 2 正在办理业务
2021-03-04 17:24:00 3 正在办理业务
2021-03-04 17:24:00 4 正在办理业务
2021-03-04 17:24:03 5 正在办理业务
2021-03-04 17:24:03 6 正在办理业务
2021-03-04 17:24:03 7 正在办理业务
2021-03-04 17:24:03 8 正在办理业务
2021-03-04 17:24:03 9 正在办理业务
2021-03-04 17:24:06 10 正在办理业务
2021-03-04 17:24:06 11 正在办理业务

4. 多线程同步之Condition(条件锁)

条件对象Condition能让一个线程A停下,等待线程B,线程B满足某个条件后又通知线程A继续执行。

示例:

import threading


class Boy(threading.Thread):
    def __init__(self, cond, name):
        super(Boy, self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        self.cond.acquire()
        print(self.name + ": 嫁给我吧!?")
        self.cond.notify()  # 唤醒一个挂起的线程,让hanmeimei表态
        self.cond.wait()  # 释放内部所占用的琐,同时线程被挂起,直至接收到通知被唤醒或超时,等待hanmeimei回答
        print(self.name + ": 我单下跪,送上戒指!")
        self.cond.notify()
        self.cond.wait()
        print(self.name + ": Li太太,你的选择太明智了。")
        self.cond.release()


class Girl(threading.Thread):
    def __init__(self, cond, name):
        super(Girl, self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        self.cond.acquire()
        self.cond.wait()  # 等待Lilei求婚
        print(self.name + ": 没有情调,不够浪漫,不答应")
        self.cond.notify()
        self.cond.wait()
        print(self.name + ": 好吧,答应你了")
        self.cond.notify()
        self.cond.release()


if __name__ == '__main__':
    cond = threading.Condition()
    boy = Boy(cond, "LiLei")
    girl = Girl(cond, "HanMeiMei")
    girl.start()
    boy.start()

输出:

LiLei: 嫁给我吧!?
HanMeiMei: 没有情调,不够浪漫,不答应
LiLei: 我单下跪,送上戒指!
HanMeiMei: 好吧,答应你了
LiLei: Li太太,你的选择太明智了。

说明:上述代码实例化了两个Thread线程对象,boy和girl,同时调用threading实例化cond对象,程序先调用start()启动gril线程,gril获取到条件变量锁cond对象,但又执行wait()方法进行阻塞并释放锁。boy线程启动后,获得条件变量锁并发出消息,之后通过notify唤醒一个阻塞的线程,并释放条件锁cond进入阻塞状态。最后通过release()方法释放条件锁cond。

5. 多线程同步之Event(事件)

事件用于线程之间的通信,一个线程发出一个信号,其他一个或者多个线程等待,调用Event对象的wait()方法,线程则会阻塞等待,调用set()方法,线程会被唤醒

示例:

import threading, time


class Boy(threading.Thread):
    def __init__(self, cond, name):
        super(Boy, self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        print(self.name + ": 嫁给我吧!?")
        self.cond.set()  # 唤醒一个挂起的线程,让hanmeimei表态
        time.sleep(0.5)
        self.cond.wait()
        print(self.name + ": 我单下跪,送上戒指!")
        self.cond.set()
        time.sleep(0.5)
        self.cond.wait()
        self.cond.clear()
        print(self.name + ": Li太太,你的选择太明智了。")


class Girl(threading.Thread):
    def __init__(self, cond, name):
        super(Girl, self).__init__()
        self.cond = cond
        self.name = name

    def run(self):
        self.cond.wait()  # 等待Lilei求婚
        self.cond.clear()
        print(self.name + ": 没有情调,不够浪漫,不答应")
        self.cond.set()
        time.sleep(0.5)
        self.cond.wait()
        print(self.name + ": 好吧,答应你了")
        self.cond.set()


if __name__ == '__main__':
    cond = threading.Event()
    boy = Boy(cond, "LiLei")
    girl = Girl(cond, "HanMeiMei")
    boy.start()
    girl.start()

输出:

LiLei: 嫁给我吧!?
HanMeiMei: 没有情调,不够浪漫,不答应
LiLei: 我单下跪,送上戒指!
HanMeiMei: 好吧,答应你了
LiLei: Li太太,你的选择太明智了。

Event内部默认设置了一个标志,初始值为False,调用Event.set()对象时将内部标志设置为True,唤醒一个线程。

6. 线程优先队列queue

Python中queue模块提供了同步的、线程安全的队列类。包含先进先出队列queue,后进先出队列LifoQueue和优先级队列Priority。这些队列都是实现了锁的原理,所以是线程安全的,可直接用来实现线程之间的同步。

示例

import threading
import time
import queue

def ProducerA():
    count = 1
    while True:
        q.put(f"冷饮 {count}")
        print(f"{time.strftime('%H:%M:%S')} A 放入:[冷饮 {count}]")
        count += 1
        time.sleep(1)


def ConsumerB():
    while True:
        print(f"{time.strftime('%H:%M:%S')} B 取出 [{q.get()}]")
        time.sleep(5)


if __name__ == '__main__':
    # 先进先出
    q = queue.Queue(maxsize=5)  # 最大容量为5
    # q = queue.LifoQueue(maxsize=3)
    # q = queue.PriorityQueue(maxsize=3)
    p = threading.Thread(target=ProducerA)
    c = threading.Thread(target=ConsumerB)
    c.start()
    p.start()

输出:

11:44:00 A 放入:[冷饮 1]
11:44:00 B 取出 [冷饮 1]
11:44:01 A 放入:[冷饮 2]
11:44:02 A 放入:[冷饮 3]
11:44:03 A 放入:[冷饮 4]
11:44:04 A 放入:[冷饮 5]
11:44:05 B 取出 [冷饮 2]
11:44:05 A 放入:[冷饮 6]
11:44:06 A 放入:[冷饮 7]
11:44:10 B 取出 [冷饮 3]
11:44:10 A 放入:[冷饮 8]
11:44:15 B 取出 [冷饮 4]
11:44:15 A 放入:[冷饮 9]
11:44:20 B 取出 [冷饮 5]
11:44:20 A 放入:[冷饮 10]

说明:queue.Queue.put()方法往队列中存入数据,get()方法从队列中获取一条数据并删除。当队列满时,put方法不再执行而是等待队列有空闲空间继续放入数据,get方法当队列为空则继续等待。

7. Pool线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
导入线程池:from multiprocess.dummy import Pool

示例:

from multiprocessing.dummy import Pool as ThreadPool
import time


def fun(n):
    time.sleep(2)


if __name__ == '__main__':

    start = time.time()
    for i in range(5):
        fun(i)
    print("单线程顺序执行耗时:", time.time() - start)

    start2 = time.time()
    # 开5个 worker,没有参数时默认是 cpu 的核心数
    pool = ThreadPool(processes=5)
    # 在线程中执行 urllib2.urlopen(url) 并返回执行结果
    results2 = pool.map(fun, range(5))
    pool.close()
    pool.join()
    print("线程池(5)并发执行耗时:", time.time() - start2)

输出:

单线程顺序执行耗时: 10.010493993759155
线程池(5)并发执行耗时: 2.0543053150177

这篇文章描述多线程挺好的,可以参考下https://www.cnblogs.com/hoojjack/p/10846010.html

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大帅不是我

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

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

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

打赏作者

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

抵扣说明:

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

余额充值