python多线程
初始案例
import threading
import time
def listen_music(name):
while True:
time.sleep(1)
print(name,"正在播放音乐")
def download_music(name):
while True:
time.sleep(2)
print(name,"正在下载音乐")
if __name__ == '__main__':
p1 = threading.Thread(target=listen_music,args=("网易云音乐",))
p2 = threading.Thread(target=download_music,args=("网易云音乐",))
p1.start()
p2.start()
观察上面的输出代码可以知道:
1.CPU是按照时间片轮询的方式来执行子线程的。cpu内部会合理分配时间片。时间片到a程序的时候,a程序如果在休眠,就会自动切换到b程序。
2.严谨来说,CPU在某个时间点,只在执行一个任务,但是由于CPU运行速度和切换速度快,因为看起来像多个任务在一起执行而已。
主线程何时结束
CPU采用时间片轮询的方式,如果轮询到子线程,发现他要休眠1s,他会先去运行主线程。所以说CPU的时间片轮询方式可以保证CPU的最佳运行
import threading
import time
def run():
for i in range(5):
time.sleep(1)
print(i)
t1 = threading.Thread(target=run)
t1.start()
print("出现位置")
#结果
出现位置
0
1
2
3
4
join()阻塞
join() 方法可以阻塞主进程(注意只能阻塞主进程,其他子进程是不能阻塞的),直到 t1 子线程执行完,再解阻塞。
import threading
import time
def test(name):
for i in range(5):
time.sleep(1)
print(name,i)
t1 = threading.Thread(target = test, args = ("test1",)) #test1这个参数值传给name
t1.start()
t1.join() #如果没有join :top or bottom 会出现在句首
print("top or bottom")
共享变量
import threading
import time
num = 0
def work1(loop,name):
global num
for i in range(loop):
num += 1
print(name,num)
def work2(loop,name):
global num
for i in range(loop):
num += 1
print(name,num)
if __name__ == "__main__":
t1 = threading.Thread(target= work1,args=(1000000,"test1",))
t2 = threading.Thread(target= work2,args=(1000000,"test2",))
t1.start()
time.sleep(1)
t2.start()
print(threading.enumerate())
#结果
test1 1000000
[<_MainThread(MainThread, started 9700)>, <Thread(Thread-2, started 8912)>]
test2 2000000
#对比
if __name__ == "__main__":
t1 = threading.Thread(target= work1,args=(1000000,"test1",))
t2 = threading.Thread(target= work2,args=(1000000,"test2",))
t1.start()
t2.start()
print(threading.enumerate())
#结果
[<_MainThread(MainThread, started 3416)>, <Thread(Thread-1, started 8320)>, <Thread(Thread-2, started 1764)>]
test1 1162510
test2 1188281
if __name__ == "__main__":
t1 = threading.Thread(target= work1,args=(1000000,"test1",))
t2 = threading.Thread(target= work2,args=(1000000,"test2",))
t1.start()
t2.start()
while len(threading.enumerate())!=1:
time.sleep(1)
print(num) ##只要t1,t2没运行完,主程序一直休眠
#结果
test2 1309758
test1 1311191
1311191
原因:奇怪了,我不是每个线程都自加一百万次吗?照理来说,应该最后的结果是200万才对的呀。问题出在哪里呢?
我们知道CPU是采用时间片轮询的方式进行几个线程的执行。
假设我CPU先轮询到work1(),num此时为100,在我运行到第10行时,时间结束了!此时,赋值了,但是还没有自加!即temp=100,num=100。
然后,时间片轮询到了work2(),进行赋值自加。num=101了。
又回到work1()的断点处,num=temp+1,temp=100,所以num=101。
就这样!num少了一次自加!
在次数多了之后,这样的错误积累在一起 结果就是1188281 这就是线程安全问题
锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
步骤
lock = threading.Lock() # 取得锁
lock.acquire() # 上锁
lock.release() # 解锁
解决线程安全问题
import threading
import time
num = 0
lock = threading.Lock()
def work1(loop,name):
global num
for i in range(loop):
lock.acquire()
num += 1
lock.release()
print(name,num)
def work2(loop,name):
global num
for i in range(loop):
lock.acquire()
num += 1
lock.release()
print(name,num)
if __name__ == "__main__":
t1 = threading.Thread(target= work1,args=(1000000,"test1",))
t2 = threading.Thread(target= work2,args=(1000000,"test2",))
t1.start()
t2.start()
while len(threading.enumerate())!=1:
time.sleep(1)
print(num) #主函数停顿一秒再输出才能得到最后的结果
#结果
test2 1926107
test1 2000000
2000000
线程test2 占用num test1一直等待运行,test2释放后,test1运行
将锁位置改变包住for循环结果:
import threading
import time
num = 0
lock = threading.Lock()
def work1(loop,name):
global num
lock.acquire() #将lock包住for循环,则必须等待一个循环结束 才进行下一个
for i in range(loop):
num += 1
lock.release()
print(num,name)
def work2(loop,name):
global num
lock.acquire()
for i in range(loop):
num += 1
lock.release()
print(num,name)
if __name__ == '__main__':
t1 = threading.Thread(target=work1,args=(1000000,'test1',))
t2 = threading.Thread(target=work1,args=(1000000,'test2',))
t1.start()
t2.start()
while len(threading.enumerate())!= 1:
time.sleep(1) #只要t1,t2没运行完,主程序一直休眠
print(num)
#结果
1000000 test1
2000000 test2
2000000
应用实例:起两个线程同时向一个文件写入内容
import threading
def func(list,name):
with open("c:test.txt",'a',encoding='utf-8') as f:
for i in list:
f.write(str(i)+"\n")
print(name+"写入:"+str(i))
print("done!")
list = list(i for i in range(1000))
t1 = threading.Thread(target=func,args=(list,'test1',))
t2 = threading.Thread(target=func,args=(list,'test2',))
if __name__ == '__main__':
t1.start()
t2.start()
#部分结果
test2写入:995
test2写入:996
test2写入:997
test2写入:998
test2写入:999
done!