Python多线程之继承threading.Thread类基本使用

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

来看一下继承threading.Thread类的基本使用

1、基本使用

继承threading.Thread父类

重写run方法

通过start运行线程

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y):
        super().__init__()
        self.x = x
        self.y = y

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

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

    def run(self):
        self.add()


if __name__ == '__main__':
    t1 = MyThread(1, 2)
    t1.start()
    t2 = MyThread(3, 4)
    t2.start()

运行

systemPath 继承 继承threading.thread_ide

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

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

创建锁:lock = threading.Lock()

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

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y, lock):
        super().__init__()
        self.x = x
        self.y = y
        self.lock: threading.Lock = lock

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

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

    def run(self):
        self.add()


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

运行

systemPath 继承 继承threading.thread_ide_02

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

通过join阻塞运行

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

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y, lock):
        super().__init__()
        self.x = x
        self.y = y
        self.lock: threading.Lock = lock

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

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

    def run(self):
        self.add()


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

运行

systemPath 继承 继承threading.thread_ide_03

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

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

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y, lock):
        super().__init__()
        self.x = x
        self.y = y
        self.lock: threading.Lock = lock

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

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

    def run(self):
        self.add()


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

运行

systemPath 继承 继承threading.thread_守护线程_04

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

systemPath 继承 继承threading.thread_守护线程_05

通过name指定线程名称

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y, lock):
        super().__init__()
        self.x = x
        self.y = y
        self.lock: threading.Lock = lock

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

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

    def run(self):
        self.add()


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

运行

systemPath 继承 继承threading.thread_守护线程_06

通过daemon设置守护线程

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

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

举几个例子说明:

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

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y, sleep, lock):
        super().__init__()
        self.x = x
        self.y = y
        self.sleep = sleep
        self.lock: threading.Lock = lock

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

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

    def run(self):
        self.add()


if __name__ == '__main__':
    MyThread.log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = MyThread(1, 2, 2, thread_lock)
    t1.name = "T1名称"
    t1.setDaemon(False)  # 设置守护线程,True是守护线程 False不是守护线程
    t1.start()
    t2 = MyThread(3, 4, 6, thread_lock)
    t2.name = "T2名称"
    t2.daemon = False  # 设置守护线程,True是守护线程 False不是守护线程
    t2.start()
    sleep_main = 4
    time.sleep(sleep_main)
    MyThread.log(f'这是main线程的睡眠时间:{sleep_main}')
    MyThread.log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

运行

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

systemPath 继承 继承threading.thread_ide_07

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

systemPath 继承 继承threading.thread_主线程_08

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

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

import datetime
import os
import threading
import time


class MyThread(threading.Thread):
    def __init__(self, x, y, sleep, lock):
        super().__init__()
        self.x = x
        self.y = y
        self.sleep = sleep
        self.lock: threading.Lock = lock

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

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

    def run(self):
        self.add()


if __name__ == '__main__':
    MyThread.log(f'这是main线程的开始:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
    thread_lock = threading.Lock()
    t1 = MyThread(1, 2, 4, thread_lock)
    t1.name = "T1名称"
    t1.setDaemon(False)  # 设置守护线程,True是守护线程 False不是守护线程
    t1.start()
    t2 = MyThread(3, 4, 6, thread_lock)
    t2.name = "T2名称"
    t2.daemon = True  # 设置守护线程,True是守护线程 False不是守护线程
    t2.start()
    sleep_main = 2
    time.sleep(sleep_main)
    MyThread.log(f'这是main线程的睡眠时间:{sleep_main}')
    MyThread.log(f'这是main线程的结束:{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')

systemPath 继承 继承threading.thread_主线程_09

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

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

systemPath 继承 继承threading.thread_主线程_10

2、总结:

使用继承threading.Thread父类方式开启线程,执行以下步骤

必须:

继承threading.Thread父类

重写run方法

通过start运行线程

非必须:

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

通过join阻塞运行

通过name指定线程名称

通过daemon设置守护线程