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