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()
运行
可以看到已经都输出了,但是顺序有问题,这是线程不同步的造成的
通过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()
运行
这样就保证了线程同步。函数中的块代码是一起执行的。
通过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")}')
运行
可以看到并没有等待任意线程就直接执行了
如果使用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")}')
运行
如果等待两个线程执行完成,则是这样
通过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")}')
运行
通过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秒
(2)t1线程执行2秒,t2线程执行6秒,主线程执行4秒,设置t2线程为守护线程,则预期情况是t1线程完成执行2秒,主线程执行4秒,t2线程执行一些代码,然后后续丢失一些代码
符合预期: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")}')
符合预期:t1线程完成执行4秒,主线程执行2秒,t2线程执行一些代码,然后后续丢失一些代码
但是这种情况不太好复现,为什么呢?因为我们用了同一个锁,导致开始t1的时候,t2必须等待t1结束才能开始,所以不好复现,可以把锁去掉,下面是去掉锁的情况
2、总结:
使用继承threading.Thread父类方式开启线程,执行以下步骤
必须:
继承threading.Thread父类
重写run方法
通过start
运行线程
非必须:
通过 threading.Lock()保证线程同步
通过join阻塞运行
通过name指定线程名称
通过daemon设置守护线程