python多线程

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!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值