文章目录

  • 前言
  • 一、什么时候使用多线程
  • 二、多线程的建立
  • 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操作,因此多线程的可操控性更强,更适合完成多线程任务。