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