Python多线程之threading.Thread()基本使用

Python多线程之threading.Thread()基本使用

在Python中有两种形式可以开启线程,一种是使用threading.Thread()方式,一种是继承thread.Thread类,来看一下threading.Thread()开启线程的基本使用。

1、threading.Thread()方式开启线程

创建threading.Thread()对象

通过target指定运行的函数

通过args指定需要的参数

通过start运行线程

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    tid = threading.current_thread().ident
    print(f"进程:[{pid}]线程:[{tid}]{msg}")


def add(x, y):
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now}")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    return x + y


if __name__ == '__main__':
    log("我是主线程")
    t1 = threading.Thread(target=add, args=(1, 2))
    t2 = threading.Thread(target=add, args=(3, 4))
    t1.start()
    t2.start()
    log("主线程完事")

运行

image-20220524134632660

可以看到已经都输出了,但是顺序有问题,这是线程不同步的造成的

通过 threading.Lock()保证线程同步

创建锁:lock = threading.Lock()

锁定和释放:lock.acquire()和lock.release()

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    tid = threading.current_thread().ident
    print(f"进程:[{pid}]线程:[{tid}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


if __name__ == '__main__':
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2))
    t2 = threading.Thread(target=add, args=(thread_lock, 3, 4))
    t1.start()
    t2.start()

运行

image-20220524135820468

这样就保证了线程同步。函数中的块代码是一起执行的。

通过join阻塞运行

如果不使用阻塞,则程序顺序执行

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    tid = threading.current_thread().ident
    print(f"进程:[{pid}]线程:[{tid}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


if __name__ == '__main__':
    log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2))
    t2 = threading.Thread(target=add, args=(thread_lock, 3, 4))
    t1.start()
    t2.start()
    log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

运行

image-20220524140231867

可以看到并没有等待任意线程就直接执行了

如果使用t1来阻塞,即等t1执行完,在执行main线程的结束语句

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    tid = threading.current_thread().ident
    print(f"进程:[{pid}]线程:[{tid}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


if __name__ == '__main__':
    log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2))
    t2 = threading.Thread(target=add, args=(thread_lock, 3, 4))
    t1.start()
    t2.start()
    t1.join()  # 等待t1执行结束再执行后面的语句
    log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

运行

image-20220524140551108

如果等待两个线程执行完成,则是这样

image-20220524140713523

通过name指定线程名称

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    t = threading.current_thread()
    print(f"进程:[{pid}]线程:[{t.ident}-{t.name}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(3)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


if __name__ == '__main__':
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2), name="1+2的线程名称")
    t2 = threading.Thread(target=add, args=(thread_lock, 3, 4), name='3+4的线程名称')
    t1.start()
    t2.start()

运行

image-20220524141055165

通过daemon设置守护线程

当一个进程中的主线程和其他非守护线程都结束时,则守护线程也会随着他们的结束而结束,不再执行后续代码

换句话说,如果一个进程中还存在主线程或者还存在非守护线程,则守护线程自己没执行完自己时,就还会继续存在,继续执行自己的代码。

举几个例子说明:

(1)加法线程执行2秒,减法线程执行6秒,主线程执行4秒,都是非守护线程的时候,则都正常执行完毕后程序才会退出

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    t = threading.current_thread()
    print(f"进程:[{pid}]线程:[{t.ident}-{t.name}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(2)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


def sub(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始减法运算")
    log(f"执行减法:{x} - {y} = {x - y}")
    time.sleep(6)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束减法运算")
    lock.release()
    return x - y


if __name__ == '__main__':
    log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2), name="", daemon=False)  # 不指定名称默认线程名称为Thread-[数字]
    t2 = threading.Thread(target=sub, args=(thread_lock, 8, 4), name='', daemon=False)  # 不指定名称默认线程名称为Thread-[数字]
    t1.start()
    t2.start()
    sleep = 4
    time.sleep(sleep)
    log(f"main在睡眠{sleep}秒")
    log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

运行

image-20220524143917534

大家都按照自己的执行需求完成了执行,加法线程执行2秒,减法线程执行6秒,主线程执行4秒

(2)加法线程执行2秒,减法线程执行6秒,主线程执行4秒,设置减法线程为守护线程,则预期情况是加法线程完成执行2秒,主线程执行4秒,减法线程执行一些代码,然后后续丢失一些代码

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    t = threading.current_thread()
    print(f"进程:[{pid}]线程:[{t.ident}-{t.name}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(2)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


def sub(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始减法运算")
    log(f"执行减法:{x} - {y} = {x - y}")
    time.sleep(6)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束减法运算")
    lock.release()
    return x - y


if __name__ == '__main__':
    log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2), name="", daemon=False)  # 不指定名称默认线程名称为Thread-[数字]
    t2 = threading.Thread(target=sub, args=(thread_lock, 8, 4), name='', daemon=True)  # 不指定名称默认线程名称为Thread-[数字]
    t1.start()
    t2.start()
    sleep = 4
    time.sleep(sleep)
    log(f"main在睡眠{sleep}秒")
    log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

运行

image-20220524144230752

符合预期:加法线程完成执行2秒,主线程执行4秒,减法线程执行一些代码(打印开始时间和执行减法),然后后续丢失一些代码(输出减法时间),这是由于4秒后主线程没了,加法线程也没了,所以减法线程就没了

(3)加法线程执行4秒,减法线程执行6秒,主线程执行2秒,设置减法线程为守护线程,则预期情况是加法线程完成执行4秒,主线程执行2秒,减法线程执行一些代码,然后后续丢失一些代码

import datetime
import os
import threading
import time


def log(msg):
    pid = os.getpid()
    t = threading.current_thread()
    print(f"进程:[{pid}]线程:[{t.ident}-{t.name}]{msg}")


def add(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始加法运算")
    log(f"执行加法:{x} + {y} = {x + y}")
    time.sleep(4)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束加法运算")
    lock.release()
    return x + y


def sub(lock, x, y):
    lock.acquire()
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},开始减法运算")
    log(f"执行减法:{x} - {y} = {x - y}")
    time.sleep(6)
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log(f"现在的时间是:{now},结束减法运算")
    lock.release()
    return x - y


if __name__ == '__main__':
    log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = threading.Thread(target=add, args=(thread_lock, 1, 2), name="", daemon=False)  # 不指定名称默认线程名称为Thread-[数字]
    t2 = threading.Thread(target=sub, args=(thread_lock, 8, 4), name='', daemon=True)  # 不指定名称默认线程名称为Thread-[数字]
    t1.start()
    t2.start()
    sleep = 2
    time.sleep(sleep)
    log(f"main在睡眠{sleep}秒")
    log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

运行

image-20220524144831245

符合预期:加法线程完成执行4秒,主线程执行2秒,减法线程执行一些代码,然后后续丢失一些代码

但是这种情况不太好复现,为什么呢?因为我们用了同一个锁,导致开始t1的时候,t2必须等待t1结束才能开始,所以不好复现,可以把锁去掉,下面是去掉t2的锁的情况

image-20220524145145343

2、总结:

使用threading.Thread()方式开启线程,执行以下步骤

必须:

  1. 创建threading.Thread()对象

  2. 通过target指定运行的函数

  3. 通过args指定需要的参数

  4. 通过start运行线程

非必须:

  • 通过 threading.Lock()保证线程同步

  • 通过join阻塞运行

  • 通过name指定线程名称

  • 通过daemon设置守护线程

posted @ 2022-05-24 15:02  南风丶轻语  阅读(21902)  评论(0编辑  收藏  举报