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

这是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了。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,635评论 0 6
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,744评论 0 8
  • 今天开始打算开一个新系列,就是python的多线程和多进程实现,这部分可能有些新手还是比较模糊的,都知道pytho...
    Aedda阅读 807评论 0 2
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,448评论 1 15