9.3並行プログラミング:マルチスレッド
9.3.1スレッドの基礎
スレッドとは何ですか?
CPUスケジューリングの最小単位、実行ユニット
どのようなマルチスレッド:
同時に開いている複数の並行スレッド(並列)実行
プロセス対スレッド
プロセスは:主なタスクは、空間、ローディング・リソースを分割するために、静的です
スレッド:動的なコードの実行、実行、
スレッドは、プロセスが複数のスレッドを含むことができ、プロセスに依存しているが、メインスレッドがなければならない、スレッドがCPUによって実行される最小単位です。
オープンマルチプロセスのオーバーヘッドが非常に大きく、
オープンスレッドのオーバーヘッドが非常に小さく、
プロセスは、10〜100倍のスレッドです
マルチプロセス遅い開きます
オープンマルチスレッド高速化
データは直接、プロセス間で共有することができない、キューで共有することができます
プロセス内のスレッド間で同じデータを共有することができます
マルチスレッド・アプリケーションのシナリオ
並行性:CPUスイッチ前後(スレッドを切り替えるため)、同時マルチプロセス、マルチスレッド
より複雑なプロセス:タスクを実行するためにメインスレッド内で、各プロセスを複数のプロセスを開きます
マルチスレッド:プロセスを開いて、複数のスレッドがタスクを実行するプロセスを
マルチプロセスを使用する場合は、時にマルチスレッドを使用するには?
プログラム:三つの異なるタスク(マルチスレッド)
仕事の経験を同時実行した後:マルチスレッド大半。
2つの方法で9.3.2オープンスレッド
from threading import Thread
def task():
print('开启线程')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print('主线程')
class MyThread(Thread):
def run(self):
print('开启线程')
if __name__ == '__main__':
t = MyThread()
t.start()
print('主线程')
スレッドとプロセス9.3.3とのコントラスト
- コントラストスピード - 絶対スレッドよりも早くプロセス
from threading import Thread
from multiprocessing import Process
def task():
print('running')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print('主线程')
'''
打印结果:
running
主线程
'''
p = Process(target=task)
p.start()
print('主进程')
'''
打印结果:
主进程
running
'''
- 比較親/子のpid - メインスレッドと子供が同じPIDスレッド
from threading import Thread
import os
def task():
print(f'子线程{os.getpid()}')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print(f'主线程{os.getpid()}')
# 打印结果
子线程 10532
主线程 10532
- スレッド間でデータを共有
from threading import Thread
num = 1000
def task():
global num
num = 1
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print(num) 打印结果: num = 1
9.3.4スレッド方法
from threading import Thread
import time
def task():
print('running')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
# time.sleep(1)
print(t.is_alive()) # 判断子线程是否存活
t.setName('Agoni') # 设置线程名字
print(t.getName()) # 获取线程名字
print('主线程')
# threading模块方法
import threading
print(threading.current_thread()) # 获取线程对象
print(threading.current_thread().name) # 获取线程名字 MainThread
print(threading.active_count()) # 获取活跃的线程数量
print(threading.enumerate()) # 返回一个列表,放置所有的线程对象
9.3.5デーモンスレッド
最初のケース
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(1) #阻塞1秒,主线程就执行完了,作为守护线程,也要立刻结束,下面的代码不执行
print(f'{name} is over') # 没有执行
if __name__ == '__main__':
t = Thread(target=task,args=('Agoni',))
t.daemon = True
t.start()
print('主线程')
# 打印结果
Agoni is running
主线程
後者の場合
from threading import Thread
import time
def foo(): # 守护线程
print(123)
time.sleep(1)
print("end123")
def bar(): # 子线程
print(456)
time.sleep(2)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon = True
t1.start()
t2.start() # 子线程,守护线程几乎同时进行
print("---main---")
# 打印结果
123
456
---main---
end123
end456
思考:デーモンスレッドは継続なぜメインスレッドはすでに、印刷されていますか?
デーモンスレッド:子供の保護者は、メインスレッドスレッド、メインスレッドが終了すると、子スレッドがすぐに終了します。
メインスレッドが終了すると?
マルチスレッドは、同じスペースで同じプロセスは、スペースとリソースの代わりにプロセスは、静的で、メインスレッドがメモリ内の生きているプロセス空間のための必要条件である、スレッドの保護者は最後まで待たなければ、メインスレッドが終了した後でなければならない、メインスレッドなければならないが待つために、すべての非デーモンスレッドは、端へのデーモンはすべての非デーモンプロセスの終わりと終わりにプライマリスレッドの後を待たなければならないので。
差分デーモンスレッドとデーモン
# 举例
from threading import Thread
import time
def foo():
print(123)
time.sleep(2)
print("end123")
def bar():
print(456)
time.sleep(1)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print("---main---")
# 打印结果
123
456
---main---
end456
# 与上一个代码比较,守护线程阻塞时间比子线程阻塞时间长,在子现场曾执行完之后,主线程也结束,守护线程也结束.所以未打印 'end123'
9.3.6スレッドのミューテックス
# 如果不加锁,让程序编程串行,要用join
from threading import Thread
import time
x = 100
def task():
global x
temp = x
time.sleep(1)
temp -= 1
x = temp
if __name__ == '__main__':
t_l = []
for i in range(100):
t = Thread(target=task)
t_l.append(t)
t.start()
for i in t_l:
i.join()
print(f'主线程{x}') 打印结果 x = 99
# 为什么得到的x结果为99?
# for循环速度很快,相当于100子线程同时拿到x=100,并赋值给temp,第一个子线程,继续执行代码,此时x=99,第二个线程拿到x=99,但是temp的值还是100,继续执行代码,再给x赋值99,接下来的子线程依次...
from threading import Thread
from threading import Lock
x = 100
def task(lock):
lock.acquire()
global x # global x 在lock.acquire()前后结构都一样
temp = x
temp -= 1
x = temp
lock.release()
if __name__ == '__main__':
lock = Lock()
t_l = []
for i in range(100):
t = Thread(target=task,args=(lock,))
t_l.append(t)
t.start()
for i in t_l:
i.join() # 主线程在所有子线程结束后再执行
print(f'主线程{x}') 打印结果 x = 0
# 第一个线程拿到 x = 100 ,接下来的每个线程拿到的都是 x ,并不是x对应的值
# 第一个线程执行完毕后, x = 99
# 第二个进程进去: x = 98,后面依次...
9.3.7デッドロックとロック再帰
デッドロックとは何ですか?
複数のスレッド(プロセス)リソースの競合は、あまりにも開いているミューテックス場合は、状況を待って、ロックをつかむためにお互いに遭遇すると、プログラムはラムを生きます。
デッドロックの原因:行のスレッド(プロセス)を与えることを数回ロックします。
# 死锁现象
from threading import Thread
from threading import Lock
import time
lock_A = Lock()
lock_B = Lock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock_A.acquire()
print(f'{self.name}拿到A锁')
lock_B.acquire()
print(f'{self.name}拿到B锁')
lock_B.release()
lock_A.release()
# 第一个子线程解A锁后,第二个子线程就会拿到A锁,同时第一个子线程会去f2拿B锁,这时候就会出现一个情况:第二个子线程等B锁,第一个子线程等A锁,程序就会夯住,这就是死锁现象
def f2(self):
lock_B.acquire()
print(f'{self.name}拿到B锁')
time.sleep(1)
lock_A.acquire()
print(f'{self.name}拿到A锁')
lock_A.release()
lock_B.release()
if __name__ == '__main__':
for i in range(3):
t = MyThread()
t.start()
print('主线程')
ソリューション・デッドロック - 再帰的なロックRLOCK
再帰ロックのみロックすることができ、レコードのロックは、限り取得は、一度カウント1にロックされるように、限り、再帰的ロック・カウントがゼロでないように、他のスレッドまたはプロセスがこのロックを参照することができない、1だけ減少1つのカウントを解放します。
from threading import Thread
from threading import RLock
import time
lock_A = lock_B = RLock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock_A.acquire()
print(f'{self.name}拿到A锁')
lock_B.acquire()
print(f'{self.name}拿到B锁')
lock_B.release()
lock_A.release()
def f2(self):
lock_B.acquire()
print(f'{self.name}拿到B锁')
time.sleep(1)
lock_A.acquire()
print(f'{self.name}拿到A锁')
lock_A.release()
lock_B.release()
if __name__ == '__main__':
for i in range(3):
t = MyThread()
t.start()
print('主线程')
9.3.8セマフォ
セマフォ:セマフォは、複数のスレッドやプロセスが同時に入ることができます
from threading import Thread
from threading import current_thread
from threading import Semaphore
import time
import random
sm = Semaphore(4)
def go_public_wc():
sm.acquire()
print(f'{current_thread().name}正在使用WC')
time.sleep(random.randint(1,3))
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=go_public_wc)
t.start()
9.3.9 GILロック
GILロック:グローバルインタプリタロックは、CPythonのはインタプリタに一度に共有リソースの使用同時シリアル、一つのスレッドだけになるだろう、ユニークなミューテックスで、GILロックが自動的にロックし、ロックを解除され、目的は、内部のデータを管理し、効率を犠牲にし、データのセキュリティを確保することです。
イラスト:IOの出会いを遮断する現在のスレッドが中断される場合は、同じプロセスの3つのスレッドを実行する最初のスレッドGILインタプリタ最初のCPUに入るために、GILは、次のスレッドを解放し、されます。 GILは通訳に入ります。
グローバルインタプリタロックを設定します。GIL
- インタプリタ(唯一のシングルコア時に開発されたPython言語)内のデータのセキュリティを確保
- 力をロック:開発の負担を軽減するために
プラスGILグローバルインタープリタロックの問題によって引き起こされます:
一つの問題:
- マルチスレッドの単一プロセスが複数のコアを利用することはできません。一つの批判を
- マルチスレッドとマルチプロセスは、マルチコアを活用することができます
質問2:
- 問題は、同時実行することはできません感じます?
議論:シングルコアマルチスレッド処理IOブロック、マルチスレッドおよびマルチコア処理効率がほぼIOをブロック
マルチプロセッサ・スレッドよりも三シングルコア処理時間IO IO 3マルチスレッド切り替え時間(ほぼ)
分析:
4つのタスクは、治療計画の選択肢を対処する必要があります。
オプション1:4つのプロセスを開きます。
オプション2:4つのスレッドを開くためのプロセス
シングルコアの場合、結果
4つのタスクではなくマルチコア並列計算、プロセスの作成のオーバーヘッドを招くプログラム、プログラム2勝に、計算集約的である場合。
4 IO集約型のタスク、大きな支出のプログラムを作成するプロセス、およびプロセスはるかに少ないスレッドのスイッチング速度、プログラム2勝の場合
マルチコアの場合、分析結果
4つのタスクは、計算集約されている場合は、マルチコア並列コンピューティング手段、一つのスレッドだけは、Python、プログラムの勝利にマルチコアへのアクセスを実行していないプロセス
4 IO集約型のタスク場合は、核IOのない量は、問題を解決することはできません、プログラム2勝
マルチコア前提で:
- IO集約型のタスクがある場合:マルチスレッド。
- より複雑なプロセス:タスクは、計算集約的である場合
同時効率はCPythonの9.3.10検証
计算密集型: 开启四个进程,四个线程验证
from multiprocessing import Process
from threading import Thread
import time
def task1():
res = 1
for i in range(1,100000000):
res += i
def task2():
res = 1
for i in range(1,100000000):
res += i
def task3():
res = 1
for i in range(1,100000000):
res += i
def task4():
res = 1
for i in range(1,100000000):
res += i
if __name__ == '__main__':
# 四个进程,四个cpu并行效率
start_time = time.time()
p1 = Process(target=task1)
p2 = Process(target=task2)
p3 = Process(target=task3)
p4 = Process(target=task4)
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
p3.join()
p4.join()
print(f'主: {time.time() - start_time}') 10.75461721420288
# 一个进程,四个线程并发效率
start_time = time.time()
p1 = Thread(target=task1)
p2 = Thread(target=task2)
p3 = Thread(target=task3)
p4 = Thread(target=task4)
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
p3.join()
p4.join()
print(f'主: {time.time() - start_time}') 20.939976692199707
IO密集型:通过大量的任务验证.
from multiprocessing import Process
from threading import Thread
import time
def task():
res = 1
time.sleep(1)
if __name__ == '__main__':
#开启200个进程(开销大,速度慢),执行IO任务,耗时 7.989548206329346
start_time = time.time()
l = []
for i in range(200):
p = Process(target=task)
l.append(p)
p.start()
for i in l:
i.join()
print(f'主: {time.time() - start_time}') 7.989548206329346
# 开启2000个线程(开销小,速度快),执行IO任务,耗时 1.023855209350586
start_time = time.time()
l = []
for i in range(200):
p = Thread(target=task)
l.append(p)
p.start()
for i in l:
i.join()
print(f'主: {time.time() - start_time}') 1.023855209350586
# 任务是IO密集型并且任务数量很大,用单进程下的多线程效率高
9.3.11 GIL锁和互斥锁的关系
GIL锁.
- 自动上锁解锁
- 保护解释器的数据安全
文件中的互斥锁:
- 手动上锁解锁
- 保护的是文件数据的安全
from threading import Thread
from threading import Lock
import time
lock = Lock()
x = 100
def task():
global x
lock.acquire()
temp = x
time.sleep(1) # 如果此处没有睡1秒的阻塞,就是串行,有阻塞就是并发
temp -= 1
x = temp
lock.release()
if __name__ == '__main__':
t_l = []
for i in range(100):
t = Thread(target=task)
t_l.append(t)
t.start()
for i in t_l:
i.join()
# 线程全部是计算密集型:当程序执行,开启100个线程时,第一个线程先要拿到GIL锁,然后拿到lock锁,释放lock锁,最后释放GIL锁.
总结:自己加互斥锁,一定要加载处理共享数据的地方,加的范围不要扩大.
9.3.12 进程池与线程池
进程(线程)池:放置进程(线程)的一个容器
池化(Pool)优点:
- 先创建好一定数量的进程或线程,节省时间,提高效率
- 实现资源复用
线程池好,进程池好? 实际指 多线程,多进程的选择:IO密集型和计算密集型
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import ThreadPoolExecutor
import time
import os
import random
def task():
print(f'{os.getpid()}准备接客')
time.sleep(random.randint(1,3))
if __name__ == '__main__':
p = ProcessPoolExecutor(max_workers=5) # 进程池 max_workers 设置进程数量,默认CPU数量
p = ThreadPoolExecutor() # 线程池 默认CPU数量*5
for i in range(20):
p.submit(task) # 给线程池放任务,也可以传参
完成一个简单的socket通信, 服务端必须与一个客户端交流完毕并且这个客户端断开连接之后,服务端才能接待下一个客户端.....不合理.
解决方案: 利用多线程
# 服务端
import socket
from threading import Thread
def conmmunication(conn):
while 1:
try:
from_client = conn.recv(1024) # 阻塞
print(f'客户端消息:{from_client.decode("utf-8")}')
to_client = input('>>>').strip()
conn.send(to_client.encode('utf-8'))
except Exception:
break
conn.close()
def customer_service():
server = socket.socket()
server.bind(('127.0.0.1', 8848))
server.listen(5)
while 1:
conn,addr = server.accept()
print(f'{addr}客户:')
p = Thread(target=conmmunication,args=(conn,))
p.start()
server.close()
if __name__ == '__main__':
customer_service()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8848))
while 1:
to_server = input('>>>').strip()
client.send(to_server.encode('utf-8'))
from_server = client.recv(1024)
print(f'服务端消息:{from_server.decode("utf-8")}')
client.close()
判断:
1.互斥锁的特点:限制被上锁的内容(代码或者其他资源)同一时刻只能被一个进程或线程使用. √
2.GIL锁是对解释器进程上的锁.√
3.GIL锁表示对CPython解释器同一时刻只能有一个进程使用它.×
4.GIL锁表示对CPython解释器同一时刻只能有一个线程使用它.√
5.GIL的存在导致多个Python进程不能同时执行.×
6.单核CPU上不能同时运行多个Python解释器进程.×
7.单核CPU上不能同时运行多个Python线程.×
8.多核CPU上不会出现Python线程数量多于CPU核心数.×
总结:
- GIL锁表示一个CPython解释器进程不论开启了多少个线程,它们不会被分配到不同的CPU上执行,必须串行执行在同一个解释器上.
- 在计算密集性程序中,使用多进程是可以提升效率的.(因为不同的进程可以运行在不同的核心上).此时使用多线程并不能提升效率.
- 在IO密集型的程序中,使用多线程是可以提升效率的.(因为IO密集型的程序往往存在等待时间,此时可以让CPU执行其他线程,进而提升效率).此时使用多进程并不能提升效率.
PS:
1.GIL只是针对解释器所上的互斥锁,即:有几个CPython解释器进程,就有几个GIL.
2.其他解释器可以运行一个进程,但是把其中的多个线程分派到多个核心上执行.
9.3.13 阻塞,非阻塞,同步,异步
程序运行中变现的状态:阻塞,运行,就绪
阻塞:程序遇到IO阻塞,会立刻停止(挂起),CPU马上切换,等到该程序的IO阻塞结束之后,再执行.
非阻塞:程序没有IO,或者遇到IO通过某种手段让CPU去执行其他的任务,尽可能的占用CPU
同步异步,站在发布任务的角度:
- 同步:任务发出去之后,要等待这个任务最终结束之后,给我一个返回值,再发布下个任务
- 异步:所有任务同时发出,就直接发布下一个任务,不需要立刻返回结果. ???
9.3.14 异步+回调机制
以爬虫举例:
浏览器做的事情很简单:浏览器封装头部,发送一个请求 --> www.taobao.com(IP地址)-->服务器获取到请求信息,分析正确 --> 返回一个文件 --> 浏览器将这个问件的代码渲染,就成了看到的页面
文件:
爬虫:利用request模块功能模拟浏览器封装头,给服务器发送一个请求,骗过服务器之后,服务器也给你返回一个文件,爬虫拿到文件,进行数据清洗(筛选),获取到你想要的信息.
爬虫分两步: 1. 爬取服务端的文件(IO阻塞) / 2. 拿到文件,进行数据分析(非IO,IO极少)
# 版本一
from concurrent.futures import ProcessPoolExecutor
import requests
import os
import time
import random
def get(url):
response = requests.get(url)
print(f'{os.getpid()}正在爬取{url}')
time.sleep(random.randint(1,3))
if response.status_code == 200:
return response.text
def parse(text):
'''对爬取回来的字符串的分析'''
print(f'{os.getpid()}分析结果:{len(text)}')
if __name__ == '__main__':
url_list = [
'http://www.taobao.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.baidu.com',
'https://www.cnblogs.com/jin-xin/articles/11232151.html',
'https://www.cnblogs.com/jin-xin/articles/10078845.html',
'http://www.sina.com.cn',
'https://www.sohu.com',
'https://www.youku.com',
]
pool = ProcessPoolExecutor(4)
obj_list = []
for url in url_list:
obj = pool.submit(get,url)
obj_list.append(obj)
pool.shutdown(wait=True)
for obj in obj_list:
parse(obj.result())
'''
串行
obj_list[0].result()
obj_list[1].result()
obj_list[2].result()
obj_list[3].result()
obj_list[4].result()
'''
# 问题出在哪里?
1. 分析结果的过程是串行,效率低.
2. 你将所有的结果全部都爬取成功之后,放在一个列表中,分析.
# 版本二
# 异步处理: 获取结果的第二种方式: 完成一个任务返回一个结果,完成一个任务,返回一个结果 并发的返回.
from concurrent.futures import ProcessPoolExecutor
import requests
import os
import time
import random
def get(url):
response = requests.get(url)
print(f'{os.getpid()}正在爬取{url}')
time.sleep(random.randint(1,3))
if response.status_code == 200:
parse(response.text)
def parse(text):
'''对爬取回来的字符串的分析'''
print(f'{os.getpid()}分析结果:{len(text)}')
if __name__ == '__main__':
url_list = [
'http://www.taobao.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.baidu.com',
'https://www.cnblogs.com/jin-xin/articles/11232151.html',
'https://www.cnblogs.com/jin-xin/articles/10078845.html',
'http://www.sina.com.cn',
'https://www.sohu.com',
'https://www.youku.com',
]
pool = ProcessPoolExecutor(4)
for url in url_list:
obj = pool.submit(get,url)
pool.shutdown(wait=True)
# 版本三: 版本二几乎完美,但是两个任务有耦合性. 再上一个基础上,对其进程解耦.
# 回调函数
import requests
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Process
import time
import random
import os
def get(url):
response = requests.get(url)
print(f'{os.getpid()} 正在爬取:{url}')
# time.sleep(random.randint(1,3))
if response.status_code == 200:
return response.text
def parse(obj):
'''
对爬取回来的字符串的分析
简单用len模拟一下.
:param text:
:return:
'''
time.sleep(1)
print(f'{os.getpid()} 分析结果:{len(obj.result())}')
if __name__ == '__main__':
url_list = [
'http://www.taobao.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.baidu.com',
'https://www.cnblogs.com/jin-xin/articles/11232151.html',
'https://www.cnblogs.com/jin-xin/articles/10078845.html',
'http://www.sina.com.cn',
'https://www.sohu.com',
'https://www.youku.com',
]
start_time = time.time()
pool = ProcessPoolExecutor(4)
for url in url_list:
obj = pool.submit(get, url)
obj.add_done_callback(parse) # 增加一个回调函数
# 现在的进程完成的还是网络爬取的任务,拿到了返回值之后,结果丢给回调函数add_done_callback,
# 回调函数帮助你分析结果
# 进程继续完成下一个任务.
pool.shutdown(wait=True)
print(f'主: {time.time() - start_time}')
回调函数是主进程帮助你实现的, 回调函数帮你进行分析任务. 明确了进程的任务:只有一个网络爬取.分析任务: 回调函数执行了.对函数之间解耦.
极值情况: 如果回调函数是IO任务,那么由于你的回调函数是主进程做的,所以有可能影响效率.
回调不是万能的,如果回调的任务是IO,那么异步 + 回调机制 不好.此时如果你要效率只能牺牲开销,再开一个线程进程池.
如果多个任务,多进程多线程处理的IO任务.
- 剩下的任务 非IO阻塞. 异步 + 回调机制
- 剩下的任务 IO << 多个任务的IO 异步 + 回调机制
- 剩下的任务 IO >= 多个任务的IO 第二种解决方式,或者两个进程线程池.
9.3.15 线程队列
- FIFO -- 先进先出
import queue
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get()) 1
print(q.get()) 2
print(q.get()) 3
- LIFO -- 后进先出
import queue
q = queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get()) 3
print(q.get()) 2
print(q.get()) 1
- 优先级队列 -- 需要元组的形式,(int,数据) int 代表优先级,数字越低,优先级越高.
import queue
q = queue.PriorityQueue(3)
q.put((5,'重要信息'))
q.put((10,'普通信息'))
q.put((0,'紧急信息'))
print(q.get()) # (0, '紧急信息')
print(q.get()) # (5, '重要信息')
print(q.get()) # (10, '普通信息')
9.3.16 事件Event
import time
from threading import Thread
from threading import current_thread
from threading import Event
event = Event() # 默认是False
def task():
print(f'{current_thread().name} 检测服务器是否正常开启....')
time.sleep(3)
event.set() # 改成了True
def task1():
print(f'{current_thread().name} 正在尝试连接服务器')
event.wait() # 轮询检测event是否为True,当其为True,继续下一行代码. 阻塞.
# event.wait(1)
# 设置超时时间,如果1s中以内,event改成True,代码继续执行.
# 设置超时时间,如果超过1s中,event没做改变,代码继续执行.
print(f'{current_thread().name} 连接成功')
if __name__ == '__main__':
t1 = Thread(target=task1,)
t2 = Thread(target=task1,)
t3 = Thread(target=task1,)
t = Thread(target=task)
t.start()
t1.start()
t2.start()
t3.start()