2.5.4.1Python-多线程

总目录:https://www.jianshu.com/p/e406a9bc93a9

Python - 子目录:https://www.jianshu.com/p/50b432cb9460

多线程

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

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

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

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()


[, , ,

[[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的执行流程

这是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()

#在命令行执行

不加锁

我们可以看到,输出结果很乱,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()

加上锁

这样结果就很nice了。

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

你可能感兴趣的:(2.5.4.1Python-多线程)