并发编程之多线程篇之二
主要内容:
一、Thread对象的其他属性或方法
二、守护线程
三、互斥锁
1️⃣ Thread对象的其他属性或方法
1、Thread实例对象的方法
isAlive():返回线程是否活动的。
getName():返回线程名。
setName():设置线程名。
2、threading模块提供的一些方法:
threading.currentThread():返回当前的线程变量。
threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
3、实例:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
from threading import Thread,currentThread,active_count,enumerate
import time
def task():
for i in range(2,5):
print('%s %d is running now!'%(currentThread().getName(),i))
time.sleep(2)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.setName('我要改名了')
print(t.isAlive()) # True(是否存活)
print(active_count()) # 此时存活的线程数为2个
print(enumerate())
t.join()
print(t.getName()) # 我要改名了
print('主线程1',currentThread().getName()) # 主线程1 MainThread
print(t.isAlive()) # False
print(active_count()) # 1 (获取存活的线程数,此时只剩下了主线程)
print(enumerate()) # 将活着的线程对象取出--> [<_MainThread(MainThread, started 868)>]
'''输出:
Thread-1 2 is running now!
True
2
[<_MainThread(MainThread, started 3288)>, <Thread(我要改名了, started 6756)>]
我要改名了 3 is running now!
我要改名了 4 is running now!
我要改名了
主线程1 MainThread
False
1
[<_MainThread(MainThread, started 3288)>]
'''
2️⃣ 守护线程
1、守护线程的理解:
无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。
2、需注意的几点:
1、运行完毕并非终止运行。
2、对主进程来说,运行完毕指的是主进程代码运行完毕。
3、对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕。
3、进一步理解:
1、主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。
2、主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
4、实例:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
from threading import Thread,active_count,enumerate
import time
# 例一:
def talk(name):
time.sleep(2)
print('%s is talking!'%name)
if __name__ == '__main__':
t = Thread(target=talk,args=('cc',))
#t.daemon = True
t.setDaemon(True) # 守护线程,必须在t,start()前设置
t.start()
print('主线程')
print(t.isAlive())
print(active_count())
print(enumerate())
'''此时主线程虽然已运行完,但线程t未结束,故主线程仍未死亡,故输出:
主线程
True
2
[<_MainThread(MainThread, started 1312)>, <Thread(Thread-1, started daemon 8292)>]
'''
思考一下,下面这个实例的执行结果是怎样的顺序?
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
# 例二;
def fun1():
print(123)
time.sleep(1)
print('123 end')
def fun2():
print(456)
time.sleep(2)
print('456 end')
if __name__ == '__main__':
t1 = Thread(target=fun1)
t2 = Thread(target=fun2)
t1.daemon = True # t1为守护线程,伴随主线程死亡而死亡
t1.start() # 发出执行信号
t2.start()
print('主线程')
print(t1.isAlive()) # True
print(t2.isAlive()) # True
print(active_count()) # 3
print(enumerate())
如果你已经知道了,可以看一下下面的答案(可以自己先运行一下再看我的结果)
'''执行顺序输出如下:
123
456
主线程
True
True
3
[<_MainThread(MainThread, started 1092)>, <Thread(Thread-1, started daemon 1004)>, <Thread(Thread-2, started 3284)>]
123 end
456 end
'''
3️⃣ 互斥锁
1、线程和进程的互斥锁含义和使用相同,在进程篇已单独解释了,就不再赘述了,
这里我们再一起回想互斥锁的核心,包括以下两点:
1、局部改并发为串行,牺牲了效率,保护了数据安全
2、不同的任务(保护不同的数据),使用不同的互斥锁
2、实例:
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong
from threading import Thread,Lock
import time
n = 100
def task(mute):
global n
mutex.acquire()# 第一个线程拿到锁后,其余线程需要等待前一个线程执行完,锁释放了,才能接着执行(即改并发为串行)
tmp = n
time.sleep(0.2) # 未加锁时,并发,效率高,所有线程都已经被赋为100了,在此等待,谁先休眠完了,就执行-1,结果就是还未来得及继续-1,n仍为99
n = tmp - 1
mute.release()
if __name__ == '__main__':
t_list = []
mutex = Lock() # 实例化一个互斥锁,核心在于局部串行(针对数据共享时,保证数据安全)
for i in range(100):
t = Thread(target=task,args=(mutex,))
t_list.append(t)
t.start()
for t in t_list:
t.join()
print('主线程',n)
# 未加锁时,输出:--> 主线程 99
# 加锁时,输出:--> 主线程 0 (耗时长,效率低,但保证了数据安全)
读书原为修身,正己才能正人正世;不修身不正己而去正人正世者,无一不是盗名欺世;你把念过的书能用上十之一二,就是很了不得的人了。——朱先生