2.5.4.1Python-多线程

总目录:https://blog.csdn.net/qq_41106844/article/details/105553392

Python - 子目录:https://blog.csdn.net/qq_41106844/article/details/105553333

 

 

多线程

我们先看一个简单的实例来查看一下我们电脑的线程:

import threading as td

 

def main():

    #查看计算机有几个线程

    print(td.active_count())

    #这几个线程的名字

    print(td.enumerate())

    #查看运行这个程序的线程

    print(td.current_thread())

 

if __name__ =='__main__':

    main()

 

[<_MainThread(MainThread, started 10032)>]

 <_MainThread(MainThread, started 10032)> 

 

之后我们加一条线程:

import threadingas td

 

def td_job():

    print('This is an added Thread,number is %s'%td.current_thread())

 

def main():

    print('This is the original thread. The number is %s' % td.current_thread())

    add_td = td.Thread(target=td_job)

    #开始线程

    add_td.start()

 

if __name__ =='__main__':

    main()

This is the original thread. The number is <_MainThread(MainThread, started 19392)> 

This is an added Thread,number is <Thread(Thread-1, started 9048)>

我们增加了一条线程,但是这个线程的执行究竟是和主线程并发执行还是并行执行呢?
我们修改一下这个实例:

import threadingas td

import time

 

def td_job():

    print("T1 start")

    #等待1秒

    time.sleep(1)

    print("T1 finish")

 

def main():

    add_td = td.Thread(target=td_job)

    #开始线程

    add_td.start()

    print('all done')

 

if __name__ =='__main__':

    main()

T1 start 

all done 

T1 finish 

在输出过程中,T1 startall done 同时出现,过去一秒后T1 finish才出现,这表示这两条线程的执行时并行执行。

如果这样的话,两条线程同时执行有些混乱,那么可不可以等待子线程先执行完再执行主线程呢。

import threadingas td

import time

 

def td_job():

    print("T1 start")

    #等待1秒

    time.sleep(1)

    print("T1 finish")

 

def main():

    add_td = td.Thread(target=td_job)

    #开始线程

    add_td.start()

    #等待子进程运行完

    add_td.join()

    print('all done')

 

if __name__ =='__main__':

    main()

T1 start 

T1 finish

all done 

这样就会先把子线程运行完再执行主线程。

下面我们再添加一个子线程:

import threadingas td

import time

 

def t1_job():

    print("T1 start")

    time.sleep(2)

    print("T1 finish")

 

def t2_job():

    print("T2 start")

    time.sleep(1)

    print("T2 finish")

 

def main():

    add_t1 = td.Thread(target=t1_job,name='T1')

    #开始线程

    add_t1.start()

    #等待子进程运行完

    add_t1.join()

 

    add_t2 = td.Thread(target=t2_job,name='T2')

    #开始线程

    add_t2.start()

    #等待子进程运行完

    add_t2.join()

    print('all done')

 

if __name__ =='__main__':

    main()

T1 start 

T1 finish 

T2 start

T2 finish   

all done 

这个程序我们来更改一下几个进程的运行顺序。

 

情况1:

如果T1,T2和主进程同时执行

add_t1 = td.Thread(target=t1_job,name='T1')

#开始线程

add_t1.start()

 

add_t2 = td.Thread(target=t2_job,name='T2')

#开始线程

add_t2.start()

 

print('all done')

T1 start 

T2 start 

all done 

T2 finish

T1 finish   

T2比T1先执行完成是因为T2执行了1s,而T1执行了2s。

 

情况2:

对T2进行join操作。

add_t1 = td.Thread(target=t1_job,name='T1')

#开始线程

add_t1.start()

 

add_t2 = td.Thread(target=t2_job,name='T2')

#开始线程

add_t2.start()

#等待子进程运行完

add_t2.join()

 

print('all done')

T1 start 

T2 start 

T2 finish 

all done 

T1 finish   

这样主线程和T1就会先等T2执行完成后再输出。

 

线程队列

如果我们有很多个线程,但是这些线程都在重复执行一个函数,那么我们需要一个东西来保持我们线程的顺序,这个东西就是队列。

import threading as td

import time

from queue import Queue

 

#我们定义一个任务方法,他会接受一个列表和一个队列,将列表处理的结果put到队列中。

def job(l,q):

    for i in range(len(l)):

        l[i] = l[i]**2

    q.put(l)

 

#线程方法,定义了一个有四个列表元素的列表,生产了四个线程,线程会同时执行job方法,将结果保存到q队列中,之后遍历队列取出结果。

def mul():

    q = Queue()

    threads = []

    data = [[1,2,3],[4,5,6],[7,8,9],[1,3,5]]

    for i in range(4):

        t = td.Thread(target=job,args=(data[i],q))

        t.start()

        threads.append(t)

    print(threads)

    for threadin threads:

        thread.join()

    results = []

    for _in range(4):

        results.append(q.get())

    print(results)

 

if __name__ =="__main__":

    mul()

 

[<Thread(Thread-1, stopped 17756)>, <Thread(Thread-2, stopped 8224)>, <Thread(Thread-3, stopped 21488)>, <Thread(Thread-4, stopped 5484)>] 

[[1, 4, 9], [16, 25, 36], [49, 64, 81], [1, 9, 25]] 

之后我们来看一个东西,这个东西就是Python的一个诟病,全局解释器锁,既GIL。

GIL

我们从三个方面看GIL:什么是GIL,他的执行流程,他对多线程的影响。

什么是GIL

GIL 是CPython 解释器中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。

GIL 的功能是在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。

接着我们在说他对工作原理前,说说他对多线程的影响,我们来看两个程序:

单线程程序:

import time

start = time.clock()

def CountDown(n):

    while n >0:

        n -=1

CountDown(100000)

print("Time used:",(time.clock() - start))

Time used: 0.004817182669318798

双线程程序:

import time

from threadingimport Thread

 

start = time.clock()

def CountDown(n):

    while n >0:

        n -=1

 

t1 = Thread(target=CountDown, args=[100000 //2])

t2 = Thread(target=CountDown, args=[100000 //2])

t1.start()

t2.start()

t1.join()

t2.join()

print("Time used:",(time.clock() - start))

Time used: 0.005911790958890454

为什么双线程的执行速度还没有单线程块呢?

我们就要来看GIL的执性流程了。

GIL的执行流程

 
20155953-8be0ee10ea33d414.png
这是GIL的执行流程

上面这张图,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。

如果仅仅要求 Python 线程在开始执行时锁住 GIL,且永远不去释放 GIL,那别的线程就都没有运行的机会。

其实,CPython 中还有另一个机制,叫做间隔式检查(check_interval),意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况,每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。

接下来再看一个程序,看一下在什么适当的情况下GIL会释放。

import threading

count =0

 

def add():

    global count

    for iin range(10 **6):

        count +=1

 

def minus():

    global count

    for iin range(10 **6):

        count -=1

 

thread1 = threading.Thread(target=add)

thread2 = threading.Thread(target=minus)

thread1.start()

thread2.start()

thread1.join()

thread2.join()

print(count)

我们执行三次:

-455479

-107294

-134711

可以看到count并不是一个固定值,说明GIL会在某个时刻释放,那么GIL具体在什么情况下释放呢:

1.执行的字节码行数到达一定阈值

2.通过时间片划分,到达一定时间阈值

3.在遇到IO操作时,主动释放

 

GIL不能绝对保证线程安全

import threading

n =0

 

def foo():

    global n

    n +=1

threads = []

for iin range(100):

    t = threading.Thread(target=foo)

threads.append(t)

for tin threads:

    t.start()

for tin threads:

    t.join()

print(n)

如果线程绝对安全,不管执行多少次,这个程序的结果都是100,但是他的结果有可能是99,甚至是98,这就表示有进程丢失了。

 

我们最后来看一下线程中的锁。

lock

我们先看一个例子

import threadingas td

 

def job1():

    global A

    for iin range(10):    

        A +=1

        print('job1',A)

 

def job2():

    global A

    for iin range(10):

    A +=10

        print('job2',A)

 

if __name__ =='__main__':

    A =0

    t1 = td.Thread(target=job1)

    t2 = td.Thread(target=job2)

    t1.start()

    t2.start()

    t1.join()

    t2.join()

#在命令行执行

 
20155953-a630872a275b30c1.png
不加锁

我们可以看到,输出结果很乱,job1的输出还和job2的输出产生了叠加,这显然不是我们想要的,所以就有了锁--lock。

import threadingas td

 

def job1():

    global A,lock

    #上锁

    lock.acquire()

    for iin range(10):

        A +=1

       print('job1',A)    

    #开锁

    lock.release()

 

def job2():

    global A,lock

    #上锁

    lock.acquire()

    for iin range(10):

        A +=10

        print('job2',A)

    #开锁

    lock.release()

 

if __name__ =='__main__':

    #产生锁

    lock = td.Lock()

    A =0

    t1 = td.Thread(target=job1)

    t2 = td.Thread(target=job2)

    t1.start()

    t2.start()

    t1.join()

    t2.join()

 
20155953-724ec48bf5eb2795.png
加上锁

这样结果就很nice了。

锁就是保证线程的安全,不会发生线程冲突。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒 暄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值