什么是线程
代码由静态变为动态运行的时候,负责执行代码的东西就叫线程
如何使用线程
一、通过threading.Thread类初始化
当主线程执行了t1.start(),此时创建子线程t1,需要执行的函数是sing。然后主线程和子线程在操作系统的时间片轮转下随机执行。当又轮到主线程时,t2.start()创建了子线程t2,此时主线程、子线程t1、子线程t2在时间片轮转下随机执行。
注意:
1、当主线程下面没有代码时,必须等待子线程结束,因为主线程一旦结束整个程序就结束了
2、当创建了线程对象t1、t2,此时没有创建子线程,只有调用start()才会有子线程,执行的代码就是target指向的函数,当子线程对应的target函数执行完成,子线程就结束了
join()的作用:
join()
会等待子线程执行完成,再执行后面的语句
import threading
import time
def sing():
time.sleep(1)
print('sing')
def dance():
time.sleep(2)
print('dance')
if __name__ == "__main__":
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
t1.join()
t2.join()
print("我是主线程")
>>>sing
>>>dance
>>>我是主线程
setDaemon()的作用
默认情况下,主线程执行完会等待子线程执行完再退出,子线程如果设置了setDaemon(True)
,主线程无需等待,直接退出,子线程也就跟着结束了
import threading
import time
def sing():
time.sleep(1)
print('sing')
def dance():
time.sleep(2)
print('dance')
if __name__ == "__main__":
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
print("我是主线程")
>>>我是主线程
二、继承自threading.Thread类
import threading
import time
class Sing(threading.Thread):
def run(self):
while True:
print("sing")
time.sleep(1)
class Dance(threading.Thread):
def run(self):
while True:
print("dance")
time.sleep(1)
if __name__ == "__main__":
t1 = Sing()
t2 = Dance()
t1.start()
t2.start()
流程跟上面的一样,只是继承了threading.Thread类,需要重写run()方法,最终还是通过start()去创建子线程并执行run()方法
线程间通信
一、主线程和子线程共享全局变量
共享全局变量的目的是让多线程之间可以协作完成任务。假设音乐播放器开启一个子线程下载歌曲,歌曲内容放到全局变量中,主线程从全局变量读取内容播放,形成了边下载边放歌的多任务
注意:下图示例代码中,如果只是获取全局变量的值,是不需要加global的,如果要修改全局变量,得看全局变量是不是可变类型,如果是可变,可以不加global直接修改,如果不可变,必须要加global才能修改
python中哪些可变,哪些不可变,以及在内存中是如何存储的,后面的文章会介绍
多线程同时修改全局变量容易出问题:
如下代码,两个线程对全局变量同时修改:
import threading
import time
num = 0
def test():
global num
for i in range(100000):
num += 1
print('t1结束')
def test1():
global num
for i in range(100000):
num += 1
print('t2结束')
if __name__ == "__main__":
t1 = threading.Thread(target=test)
t2 = threading.Thread(target=test1)
t1.start()
t2.start()
time.sleep(1)
print(num)
>>>t1结束
>>>t2结束
>>>176111
理论结果应该是20000,实际只有17万多,为什么会出现这种情况?
查看字节码:
import dis
num = 0
def numAdd():
global num
num += 1
dis.dis(numAdd)
>>> 0 LOAD_GLOBAL 0 (num)
3 LOAD_CONST 1 (1)
6 INPLACE_ADD
7 STORE_GLOBAL 0 (num)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
num +=1被翻译成了前4条指令,当num=0时,两个线程同时执行,t1如果还没有来得及保存,由于时间片轮转,就先去执行了t2,此时t2的数据保存成功,num=1,又轮到t1继续执行,此时t1开始保存之前的值,num还是等于1。因此会出现跟理论值不符的情况,这种情况下多线程是不安全的。
二、通过队列实现通信
队列是线程安全的,具体原理需要再查看下队列的底层实现
import threading
from queue import Queue
def download_data(q):
data = [1,2,3,4]
#向队列中写入数据
for temp in data:
q.put(temp)
def deal_data(q):
#从队列中获取数据
resList = list()
while True:
data = q.get()
resList.append(data)
if q.empty():
break
print(resList)
def main():
#1、创建一个队列
q = Queue()
#2、创建多个进程,将队列的引用传递过去
t1 = threading.Thread(target=download_data,args=(q,))
t2 = threading.Thread(target=deal_data,args=(q,))
t1.start()
t2.start()
if __name__ == "__main__":
main()
网友评论