概念补充:
- 在Python多线程编程中,join方法的作用是线程同步
- 守护线程,是为守护别人而存在,当设置为守护线程后,被守护的主线程不存在后,守护线程也自然不存在
以下分5种不同的形式解释join在多线程编程中的用处
第一种:Python多线程默认情况
Python多线程的默认情况(设置线程setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束
import threading, time
def doWaiting1():
print('start waiting1: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(3)
print("线程1奉命报道")
print('stop waiting1: ' + time.strftime('%H:%M:%S') + "\n")
def doWaiting2():
print('start waiting2: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(8)
print("线程2奉命报道")
print('stop waiting2: ', time.strftime('%H:%M:%S') + "\n")
tsk = []
# 创建并开启线程1
thread1 = threading.Thread(target = doWaiting1)
thread1.start()
tsk.append(thread1)
# 创建并开启线程2
thread2 = threading.Thread(target = doWaiting2)
thread2.start()
tsk.append(thread2)
# 计时程序
print('start join: ' + time.strftime('%H:%M:%S') )
print('end join: ' + time.strftime('%H:%M:%S') )
运行结果
start waiting1: 20:03:30
start waiting2: 20:03:30
start join: 20:03:30
end join: 20:03:30
线程1奉命报道
stop waiting1: 20:03:33
线程2奉命报道
stop waiting2: 20:03:38
结论:
1、计时程序属于主线程,整个主线程在开启线程1和线程2后,进入计时模块,主线程结束
2、主线程结束,但并没有影响线程1和线程2的运行,故后面线程1和线程2仍然跑来报道,至此整个程序才完全结束
第二种:开启守护线程
开启线程的setDaemon(True),设置子线程为守护线程,实现主程序结束,子程序立马全部结束功能
import threading, time
def doWaiting1():
print('start waiting1: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(3)
print("线程1奉命报道")
print('stop waiting1: ' + time.strftime('%H:%M:%S') + "\n")
def doWaiting2():
print('start waiting2: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(8)
print("线程2奉命报道")
print('stop waiting2: ', time.strftime('%H:%M:%S') + "\n")
tsk = []
# 创建并开启线程1
thread1 = threading.Thread(target = doWaiting1)
thread1.setDaemon(True)
thread1.start()
tsk.append(thread1)
# 创建并开启线程2
thread2 = threading.Thread(target = doWaiting2)
thread2.setDaemon(True)
thread2.start()
tsk.append(thread2)
print('start join: ' + time.strftime('%H:%M:%S') )
print('end join: ' + time.strftime('%H:%M:%S') )
运行结果:
start waiting1: 20:10:04
start waiting2: 20:10:04
start join: 20:10:04
end join: 20:10:04
结论:
1、主线程结束后,无论子线程1,2是否运行完成,都结束不再往下继续运行
第三种:加入join方法设置同步
当不给程序设置守护进程时,主程序将一直等待子程序全部运行完成才结束
import threading, time
def doWaiting1():
print('start waiting1: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(3)
print("线程1奉命报道")
print('stop waiting1: ' + time.strftime('%H:%M:%S') + "\n")
def doWaiting2():
print('start waiting2: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(8)
print("线程2奉命报道")
print('stop waiting2: ', time.strftime('%H:%M:%S') + "\n")
tsk = []
# 创建并开启线程1
thread1 = threading.Thread(target = doWaiting1)
thread1.start()
tsk.append(thread1)
# 创建并开启线程2
thread2 = threading.Thread(target = doWaiting2)
thread2.start()
tsk.append(thread2)
print('start join: ' + time.strftime('%H:%M:%S') )
for t in tsk:
print('%s线程到了'%t)
t.join()
print('end join: ' + time.strftime('%H:%M:%S') )
运行结果:
start waiting1: 20:14:35
start waiting2: 20:14:35
start join: 20:14:35
<Thread(Thread-1, started 19648)>线程到了
线程1奉命报道
stop waiting1: 20:14:38
<Thread(Thread-2, started 24056)>线程到了
线程2奉命报道
stop waiting2: 20:14:43
end join: 20:14:43
结论:
1、使用join函数,主线程将被阻塞,一直等待被使用了join方法的线程运行完成
2、start join 是在35秒,stop waiting1在38秒,刚好sleep了3秒,stop waiting2是43秒,刚好sleep了8秒,这也说明,线程1和2是基本同时运行的,但由于执行所消耗的时间不一致,所以阻塞所用的时间也是不一样的,最终end join时间是最后线程运行完,整个程序就中止在43秒
3、将所有的线程放入一个列表,通过循环对列表中的所有线程使用join方法判断,也是为了保证全部子线程都能全部运行完成,主线程才退出
第四种:不设置守护进程但join设置超时
给join设置timeout数值,判断等待多后子线程还没有完成,则主线程不再等待
import threading, time
def doWaiting1():
print('start waiting1: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(2)
print("线程1奉命报道")
print('stop waiting1: ' + time.strftime('%H:%M:%S') + "\n")
def doWaiting2():
print('start waiting2: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(8)
print("线程2奉命报道")
print('stop waiting2: ', time.strftime('%H:%M:%S') + "\n")
tsk = []
# 创建并开启线程1
thread1 = threading.Thread(target = doWaiting1)
thread1.start()
tsk.append(thread1)
# 创建并开启线程2
thread2 = threading.Thread(target = doWaiting2)
thread2.start()
tsk.append(thread2)
print('start join: ' + time.strftime('%H:%M:%S') )
for t in tsk:
print("开始:"+time.strftime('%H:%M:%S'))
print('%s线程到了'%t)
t.join(5)
print("结束:" + time.strftime('%H:%M:%S'))
print('end join: ' + time.strftime('%H:%M:%S') )
运行结果:
start waiting1: 21:14:25
start waiting2: 21:14:25
start join: 21:14:25
开始:21:14:25
<Thread(Thread-1, started 22348)>线程到了
线程1奉命报道
stop waiting1: 21:14:27
结束:21:14:27
开始:21:14:27
<Thread(Thread-2, started 13164)>线程到了
结束:21:14:32
end join: 21:14:32
线程2奉命报道
stop waiting2: 21:14:33
结论:
1、给join设置等待时间后,超过了等待时间后,主线程终止,但不影响子线程继续运行,等子线程全部运行完毕整个程序终止
2、所有线程可用的等待时间 timeout_total ≤ timeout * 线程数量
3、虽然timeout设置的是5s,但是线程1只需要2s,所以循环从开始到结束,只需消耗2s(21:14:27 - 21:14:25),到此循环就进入第二次,第二次等待仍可以分配5s(21:14:32 - 21:14:27),所以两次总共的等待是时间2+5=7s,但是线程2运行所需要时间是8s,而且8s是从21:14:25开始的,结束时间是21:14:33,因为join等待时间完了主程序结束了,但不影响线程2继续运行,所以在end join后,线程2仍然输出了报道结果(是因为没有开启守护线程)
4、个别文章解释join这个时间为:主线程会等待多个线程的timeout累加和,这个说法不准确,由3的推理可以得出,并非会一定等待“线程数* timeout”这么多时间,而是≤“线程数*timeout”,
第五种:开启守护线程,同事给join设置超时
超时未处理完毕的子线程将被直接终止
import threading, time
def doWaiting1():
print('start waiting1: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(2)
print("线程1奉命报道")
print('stop waiting1: ' + time.strftime('%H:%M:%S') + "\n")
def doWaiting2():
print('start waiting2: ' + time.strftime('%H:%M:%S') + "\n")
time.sleep(8)
print("线程2奉命报道")
print('stop waiting2: ', time.strftime('%H:%M:%S') + "\n")
tsk = []
# 创建并开启线程1
thread1 = threading.Thread(target = doWaiting1)
thread1.setDaemon(True)
thread1.start()
tsk.append(thread1)
# 创建并开启线程2
thread2 = threading.Thread(target = doWaiting2)
thread2.setDaemon(True)
thread2.start()
tsk.append(thread2)
print('start join: ' + time.strftime('%H:%M:%S') )
for t in tsk:
print("开始:"+time.strftime('%H:%M:%S'))
print('%s线程到了'%t)
t.join(5)
print("结束:" + time.strftime('%H:%M:%S'))
print('end join: ' + time.strftime('%H:%M:%S') )
运行结果:
start waiting1: 21:24:14
start waiting2: 21:24:14
start join: 21:24:14
开始:21:24:14
<Thread(Thread-1, started daemon 9060)>线程到了
线程1奉命报道
stop waiting1: 21:24:16
结束:21:24:16
开始:21:24:16
<Thread(Thread-2, started daemon 13912)>线程到了
结束:21:24:21
end join: 21:24:21
结论:
1、相比第四种,超时后主线程运行到end join则结束了,子线程2已经被终止停止运行