文章目录
- 前言
- 一、什么时候使用多线程
- 二、多线程的建立
- 1.def函数后threading调用
- 2.class继承Thread
- 三、多线程注意事项
- 1.线程的级别
- 2.线程的顺序
- 1.插入线程
- 2.线程锁RLock
- 3.条件锁Condition
前言
看了多篇其他优秀作者写的pyhton多线程使用教程,收获良多。
但为了加深印象和结合工作,本人整理了一下写下如下文章。
一、什么时候使用多线程
简单来说,就是想同时进行多个任务时才使用。
例如,你想在一台测量设备上尽可能快的执行完成测量任务,提升机器UPH。
若是一个单线程程序运行的话,机械结构想要运动到下一个工位需要上一步计算完成才能进行。
而多线程却是可以一边运动,一边后台处理数据,减少的就是等待处理数据的时间。
假设一个产品计算时间要0.1s,那生产线上成千上万的产品能节约下来几十分钟,而这段时间又可以多测量千个产品,正是所需求的效率提升。
二、多线程的建立
1.def函数后threading调用
先定义函数后使用threading.Thread启用多线程(其中t1为子线程):
import threading
import time
def son_thread():
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=son_thread, daemon=True)
t1.start()
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
结果:
son_thread: 0 1660023920.0654948
main: 0 1660023920.0654948
main: 1 1660023921.065916
son_thread: 1 1660023921.0669172
main: 2 1660023922.0668168
son_thread: 2 1660023922.0678182
main: 3 1660023923.0677087
son_thread: 3 1660023923.0687096
main: 4 1660023924.0686162
son_thread: 4 1660023924.069619
main: 5 1660023925.0695179
son_thread: 5 1660023925.0705192
2.class继承Thread
定义一个类继承threading.Thread,并修改其中的run函数以达到目的(需后期设定Daemon):
import threading
import time
class SonThread(threading.Thread):
def __init__(self):
super(SonThread, self).__init__()
def run(self):
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
if __name__ == '__main__':
t1 = SonThread()
t1.start()
# t1.setDamon(True)
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
结果:
son_thread: 0 1660023967.0243223
main: 0 1660023967.0243223
main: 1 1660023968.0252142
son_thread: 1 1660023968.0252142
main: 2 1660023969.0261147
son_thread: 2 1660023969.0321202
main: 3 1660023970.032028
son_thread: 3 1660023970.0330288
main: 4 1660023971.0329285
son_thread: 4 1660023971.03393
main: 5 1660023972.033332
son_thread: 5 1660023972.0343332
son_thread: 6 1660023973.0352252
son_thread: 7 1660023974.0361254
son_thread: 8 1660023975.0370257
son_thread: 9 1660023976.0379262
结果为何不同?难道是def自定义和class继承的方法导致的吗?
不,其实有一个很重要的参数————daemon的设置
。
当Daemon为True时,即使该线程还在运行,当父级线程运行完毕时就会立即终止该线程。
当Daemon为False时,即使父级线程停止运行,当该线程还在运行时就继续运行直到该线程结束为止。
三、多线程注意事项
1.线程的级别
例举一个三线程:
import threading
import time
def son_thread():
t2 = threading.Thread(target=son2_thread, daemon=True)
t2.start()
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
def son2_thread():
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=son_thread, daemon=True)
t1.start()
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
结果:
main: 0 1660024998.5236511
son2_thread: 0 1660024998.5236511
son_thread: 0 1660024998.5236511
son_thread: 1 1660024999.5245545
son2_thread: 1 1660024999.5245545
main: 1 1660024999.5245545
main: 2 1660025000.5249736
son_thread: 2 1660025000.5249736
son2_thread: 2 1660025000.5249736
son_thread: 3 1660025001.5258737
son2_thread: 3 1660025001.5258737
main: 3 1660025001.5258737
son_thread: 4 1660025002.526774
son2_thread: 4 1660025002.526774
main: 4 1660025002.526774
main: 5 1660025003.5276744
son_thread: 5 1660025003.5276744
son2_thread: 5 1660025003.5276744
可见,son_thread是main的子线程,son2_thread是son_thread的子线程。
因为daemon都为True,故main运行至5本程序就结束运行了。
更改一下son2_thread的daemon:
import threading
import time
def son_thread():
t2 = threading.Thread(target=son2_thread, daemon=False)
t2.start()
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
def son2_thread():
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
if __name__ == '__main__':
t1 = threading.Thread(target=son_thread, daemon=True)
t1.start()
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
结果:
main: 0 1660026556.6917222
son2_thread: 0 1660026556.6927197
son_thread: 0 1660026556.6927197
main: 1 1660026557.6926196
son_thread: 1 1660026557.6936202
son2_thread: 1 1660026557.6936202
main: 2 1660026558.6935287
son_thread: 2 1660026558.694529
son2_thread: 2 1660026558.694529
main: 3 1660026559.6944284
son2_thread: 3 1660026559.6954293
son_thread: 3 1660026559.6954293
main: 4 1660026560.6953287
son2_thread: 4 1660026560.6963298
son_thread: 4 1660026560.6963298
main: 5 1660026561.6962245
son2_thread: 5 1660026561.6972253
son_thread: 5 1660026561.6972253
son_thread: 6 1660026562.6981258
son2_thread: 6 1660026562.6981258
son_thread: 7 1660026563.6990178
son2_thread: 7 1660026563.6990178
son_thread: 8 1660026564.699918
son2_thread: 8 1660026564.699918
son_thread: 9 1660026565.7008188
son2_thread: 9 1660026565.7008188
son2_thread: 10 1660026566.7017188
son2_thread: 11 1660026567.7026193
son2_thread: 12 1660026568.7035282
son2_thread: 13 1660026569.7044284
son2_thread: 14 1660026570.7053282
按照daemon的定义,预想结果应该是son2_thread输出到14,son_thread输出到5,main输出到5才对。
那为什么是son2_thread输出到14,son_thread输出到9,main输出到5。
原因则是在于son2_thread的sleep为1s,使运行时间足足有15s得以让son_thread的for循环运行完成。
更改son2_thread的休息时间:
# 更改为sleep0.2秒
def son2_thread():
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(0.2)
结果为:
main: 0 1660027812.419839
son2_thread: 0 1660027812.419839
son_thread: 0 1660027812.419839
son2_thread: 1 1660027812.6210198
son2_thread: 2 1660027812.8212
son2_thread: 3 1660027813.0213716
son2_thread: 4 1660027813.22156
main: 1 1660027813.4207392
son_thread: 1 1660027813.4207392
son2_thread: 5 1660027813.42174
son2_thread: 6 1660027813.621921
son2_thread: 7 1660027813.822092
son2_thread: 8 1660027814.0222723
son2_thread: 9 1660027814.2224605
son_thread: 2 1660027814.4216397
main: 2 1660027814.4216397
son2_thread: 10 1660027814.4226408
son2_thread: 11 1660027814.6228213
son2_thread: 12 1660027814.8230007
son2_thread: 13 1660027815.0231724
son2_thread: 14 1660027815.2233527
son_thread: 3 1660027815.4225316
main: 3 1660027815.4225316
main: 4 1660027816.4234319
son_thread: 4 1660027816.4234319
main: 5 1660027817.4243324
son_thread: 5 1660027817.4243324
即为预想结果:son2_thread输出到14,son_thread输出到5,main输出到5
2.线程的顺序
1.插入线程
threading.join(timeout=None)
使用t1.join()
就可以暂停原线程运行t1线程,知道t1运行完毕才会执行回原线程。
而输入timeout时间(单位:秒)则是t1运行timeout时间后就继续执行原线程,相当于只堵塞原线程timeout秒。
举例如下:
import threading
import time
def son_thread():
t2 = threading.Thread(target=son2_thread, daemon=False)
t2.start()
t2.join()
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
def son2_thread():
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(0.2)
if __name__ == '__main__':
t1 = threading.Thread(target=son_thread, daemon=True)
t1.start()
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
结果:
main: 0 1660030861.786742
son2_thread: 0 1660030861.786742
son2_thread: 1 1660030861.986923
son2_thread: 2 1660030862.1870947
son2_thread: 3 1660030862.3872745
son2_thread: 4 1660030862.5874631
main: 1 1660030862.7876344
son2_thread: 5 1660030862.7876344
son2_thread: 6 1660030862.987815
son2_thread: 7 1660030863.1880033
son2_thread: 8 1660030863.3881834
son2_thread: 9 1660030863.5883634
son2_thread: 10 1660030863.7885435
main: 2 1660030863.7885435
son2_thread: 11 1660030863.9887154
son2_thread: 12 1660030864.1888952
son2_thread: 13 1660030864.3890755
son2_thread: 14 1660030864.5892553
main: 3 1660030864.7894356
son_thread: 0 1660030864.7894356 # 观察此处
son_thread: 1 1660030865.7903452
main: 4 1660030865.7903452
main: 5 1660030866.7912447
son_thread: 2 1660030866.7912447
son_thread: 3 1660030867.7921453
可见son2_thread运行完成才运行son_thread,结束程序前son_thread最终结果为3,被son2_thread阻塞了3s。
更改join里的timeout参数:
import threading
import time
def son_thread():
t2 = threading.Thread(target=son2_thread, daemon=False)
t2.start()
t2.join(2) #改为只堵塞2s
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
def son2_thread():
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(0.2)
if __name__ == '__main__':
t1 = threading.Thread(target=son_thread, daemon=True)
t1.start()
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
结果:
main: 0 1660031122.6203365
son2_thread: 0 1660031122.6203365
son2_thread: 1 1660031122.8215175
son2_thread: 2 1660031123.0216978
son2_thread: 3 1660031123.2218764
son2_thread: 4 1660031123.4220493
main: 1 1660031123.621237
son2_thread: 5 1660031123.622238
son2_thread: 6 1660031123.8224094
son2_thread: 7 1660031124.022598
son2_thread: 8 1660031124.222778
son2_thread: 9 1660031124.4229581
son_thread: 0 1660031124.6211364 #观察此处
main: 2 1660031124.6221373
son2_thread: 10 1660031124.6231382
son2_thread: 11 1660031124.8233182
son2_thread: 12 1660031125.0234897
son2_thread: 13 1660031125.2236702
son2_thread: 14 1660031125.42385
son_thread: 1 1660031125.6220286
main: 3 1660031125.6230295
son_thread: 2 1660031126.6229374
main: 4 1660031126.6239383
son_thread: 3 1660031127.6238377
main: 5 1660031127.6248386
son_thread: 4 1660031128.6247377
即程序开始后两秒钟son_thread就继续运行了,之后便可按照需求使用join(timeout=)参数。
2.线程锁RLock
lock = threading.RLock() # 创建RLock变量
lock.acquire() # 开启线程锁
lock.release() # 释放线程锁
在多线程的运行中,某些线程的有些变量(全局变量)需要等待其他线程计算完毕后调用才符合目标需求。
也就是需要暂时暂停本线程,等待目标线程计算完成变量后再继续运行。
这时,便需要线程锁threading.Lock,不过我更习惯用threading.RLock,会更加方便,不过还有更加方便的Condition条件锁。
被lock.acquire()锁定的线程会优先执行,但有多个锁的情况下,执行完后不释放lock.release() 将照成卡锁。
举例,三线程锁:
import threading
import time
def son_thread():
lock.acquire() # son_thread直接锁住,但是son2_thread有锁,这样不会运行son2_thread先,直接运行for循环
t2 = threading.Thread(target=son2_thread, daemon=False)
t2.start()
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
lock.release() # 解除son_thread的锁,然后对比锁的优先级,主线程锁的级别高,先运行main
def son2_thread():
lock.acquire()
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(0.2)
lock.release()
if __name__ == '__main__':
lock = threading.RLock() # 创建RLock变量
t1 = threading.Thread(target=son_thread, daemon=False) # 先运行son_thread
t1.start()
lock.acquire() # 主线程锁
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
lock.release() # 释放锁后运行低一级的锁son2_thread
结果:
son_thread: 0 1660117747.3167498 # 先是运行son_thread
son_thread: 1 1660117748.3176587
son_thread: 2 1660117749.318559
son_thread: 3 1660117750.3189812
son_thread: 4 1660117751.319873
son_thread: 5 1660117752.3207734
son_thread: 6 1660117753.3216736
son_thread: 7 1660117754.3225815
son_thread: 8 1660117755.3234832
son_thread: 9 1660117756.3243835
main: 0 1660117757.3252838 # 然后是main
main: 1 1660117758.3261757
main: 2 1660117759.3270848
main: 3 1660117760.3275046
main: 4 1660117761.328405
main: 5 1660117762.3293054
son2_thread: 0 1660117763.3301973 # 最后是son2_thread
son2_thread: 1 1660117763.5303774
son2_thread: 2 1660117763.7305658
son2_thread: 3 1660117763.930746
son2_thread: 4 1660117764.1309261
son2_thread: 5 1660117764.3311062
son2_thread: 6 1660117764.5312862
son2_thread: 7 1660117764.7314665
son2_thread: 8 1660117764.9316463
son2_thread: 9 1660117765.1318266
son2_thread: 10 1660117765.3320053
son2_thread: 11 1660117765.5321891
son2_thread: 12 1660117765.7323666
son2_thread: 13 1660117765.9325469
son2_thread: 14 1660117766.1327264
同时,RLock的先后顺序很重要,同上的程序,只是改了一下acquire的位置:
import threading
import time
def son_thread():
lock.acquire()
t2 = threading.Thread(target=son2_thread, daemon=False)
t2.start()
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
lock.release()
def son2_thread():
lock.acquire()
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(0.2)
lock.release()
if __name__ == '__main__':
lock = threading.RLock()
lock.acquire() # 只改了这里
t1 = threading.Thread(target=son_thread, daemon=False)
t1.start()
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
lock.release()
结果:
main: 0 1660119593.33683
main: 1 1660119594.3372352
main: 2 1660119595.3381352
main: 3 1660119596.3390431
main: 4 1660119597.3399446
main: 5 1660119598.340845
son_thread: 0 1660119599.3417368
son_thread: 1 1660119600.3426373
son_thread: 2 1660119601.3435376
son_thread: 3 1660119602.3444383
son_thread: 4 1660119603.3453386
son_thread: 5 1660119604.3462462
son_thread: 6 1660119605.3466506
son_thread: 7 1660119606.3475437
son_thread: 8 1660119607.3484445
son_thread: 9 1660119608.349353
son2_thread: 0 1660119609.3502448
son2_thread: 1 1660119609.550425
son2_thread: 2 1660119609.750605
son2_thread: 3 1660119609.9507937
son2_thread: 4 1660119610.1509738
son2_thread: 5 1660119610.3511536
son2_thread: 6 1660119610.551334
son2_thread: 7 1660119610.7515142
son2_thread: 8 1660119610.9516854
son2_thread: 9 1660119611.1518655
son2_thread: 10 1660119611.3520458
son2_thread: 11 1660119611.5522342
son2_thread: 12 1660119611.7524061
son2_thread: 13 1660119611.952586
son2_thread: 14 1660119612.1527755
可以看出是按顺序main——son_thread——son2_thread进行输出。
其实就是三个线程锁都放在了函数最开头,这时判断谁的级别最高
,自然也就按这种爷父子顺序排了下来。
3.条件锁Condition
cond = threading.Condition() # 创建条件锁变量
cond.acquire() # 上线程锁(等同于RLock)
cond.release() # 解线程锁(等同于RLock)
cond.wait(timeout/s) # 等待指示notify或者timeout
cond.wait_for() # 等待事件或timeout
cond.notify(num) # 通知线程,不设置输入则是第一个子级线程
cond.notifyAll() # 通知所有等待线程
使用了wait(),程序便会帮你跳转到除外的最高级线程运行,而在那运行的线程使用notify()后便会重新激活主线程。
举例如下:
import threading
import time
def son_thread():
lock.acquire() # son_thread的优先级高
t2 = threading.Thread(target=son2_thread, daemon=False)
t2.start()
for i in range(10):
print('son_thread: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5: # 当i=5时,通知主线程继续运行
lock.notify()
lock.release() # 如果不使用release,要该线程完结才能运行主线程
def son2_thread():
lock.acquire()
lock.wait() # 等待main线程给出指令
for i in range(15):
print('son2_thread: %i %s\n' % (i, time.time()))
time.sleep(0.2)
lock.release()
if __name__ == '__main__':
lock = threading.Condition() # 更改为条件锁
lock.acquire()
t1 = threading.Thread(target=son_thread, daemon=False)
t1.start()
lock.wait() # 使用等待后会暂停改线程,跳转到除外的最高级线程运行
for i in range(100):
print('main: %i %s\n' % (i, time.time()))
time.sleep(1)
if i == 5:
break
lock.notify() # 对son2_thread发送指令
lock.release()
结果:
son_thread: 0 1660546863.6640337 # 先运行son_thread
son_thread: 1 1660546864.664934
son_thread: 2 1660546865.6658342
son_thread: 3 1660546866.6667347
son_thread: 4 1660546867.6676352
son_thread: 5 1660546868.668534
son_thread: 6 1660546869.6694355
main: 0 1660546869.6694355 # 当i=5时,son_thread线程通知main线程,同时继续运行for循环
main: 1 1660546870.6703358
son_thread: 7 1660546870.6703358
son_thread: 8 1660546871.671236
main: 2 1660546871.671236
son_thread: 9 1660546872.6721365
main: 3 1660546872.6721365
main: 4 1660546873.673037
main: 5 1660546874.6740346
son2_thread: 0 1660546875.6748295 # main线程的for循环break后通知son2_thread,运行完成后结束
son2_thread: 1 1660546875.8750095
son2_thread: 2 1660546876.0751903
son2_thread: 3 1660546876.2754717
son2_thread: 4 1660546876.4756467
son2_thread: 5 1660546876.6757128
son2_thread: 6 1660546876.8759012
son2_thread: 7 1660546877.076081
son2_thread: 8 1660546877.276261
son2_thread: 9 1660546877.4764419
son2_thread: 10 1660546877.6766222
son2_thread: 11 1660546877.8767931
son2_thread: 12 1660546878.0769815
son2_thread: 13 1660546878.2771614
son2_thread: 14 1660546878.4773417
Condition相比于RLock,多了wait和notify操作,因此多线程的可操控性更强,更适合完成多线程任务。