并行,并发,多线程,GIL全局解释器锁

Posted dayle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并行,并发,多线程,GIL全局解释器锁相关的知识,希望对你有一定的参考价值。

串行

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。是串行,必须执行完一个执行一个。

并发

你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发,交叉执行。

并行

你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行,同时执行。

并发的关键是你有处理多个任务的能力,不一定要同时。

并行的关键是你有同时处理多个任务的能力

多任务

同时干多件事, 有几核cpu就能同时干几件事

线程

最小的调度单位,线程是并发的,

多线程

多线程类似于同时执行多个不同程序,多线程运行有如下特点:

1.无序性

2.线程可以被抢占(中断)

3.在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。

4.线程的并发是利用cpu上下文的切换

5.多线程开销小,比较适合在IO密集的代码里使用。

要设置多线程,需要调用模块

import threading

 实现多线程:

import threading        #调用多线程模块
import time
def test1():
     for i in range(10):
         time.sleep(1)
         print(‘test1=========>%s‘ % i)


def test2():
     for i in range(10):
         time.sleep(1)
         print(‘test2=========>%s‘ % i)

 t1 = threading.Thread(target=test1)        #开启一个多线程将test1函数传入并起别名
 t2 = threading.Thread(target=test2)
 t1.start()     #开启t1线程
 t2.start()
 test1()
 test2()
执行结果

执行顺序,——> test1 ——> test2 ——> test1 ——> test2 ——> ....

test2=========>0
test1=========>0
test2=========>1
test1=========>1
test2=========>2
test1=========>2
test1=========>3
test2=========>3
test2=========>4
test1=========>4

 证明多线程的无序性

def test1(n):
    time.sleep
    print(‘task‘, n)
for i in range(5):
    t = threading.Thread(target=test1,args=(‘t-%s‘ % i,))   给test1函数传入实参(‘t-%s‘ % i,),括号内最后的’,’不可缺少
    t.start()

 结果:无序

task t-1
task t-3
task t-0
task t-2
task t-4

 多线程共享全局变量

 

全局变量:

这是因为,在一个作用域里面给一个变量赋值的时候,Python自动认为这个变量是这个作用域的本地变量,并屏蔽作用域外的同名的变量。 所以我们会得到这样 UnboundLocalError 的一个异常。

使用全局变量的话不需要声明,给全局变量赋值的话就需要声明了。 一般全局变量在一个函数中是不可以调用的,但是声明的话,就可以在函数中调用全局函数

globa 变量名 :声明全局变量

num = 0
def update():
    global num  #global声明全局变量
    for i in range(10):
        num += 1

def reader():
    global num
    print(num)

t1 = threading.Thread(target=update)
t2 = threading.Thread(target=reader)
t1.start()
t2.start()

 结果

10

 

GIL全局解释器锁

只要在进行耗时的IO操作的时候,能释放GIL, 所以只要在IO密集型的代码里,用多线程就很合适

import threading
global_num = 0
def test1():
    global global_num
    for i in range(1000000):
        global_num += 1
    print("test1", global_num,threading.current_thread())   #打印当前线程的信息

def test2():
    global global_num
    for i in range(1000000):
        global_num += 1
    print("test2", global_num,threading.current_thread())
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
t2.start()
# t1.join() # 执行完t1在执行主函数
# t2.join() # 执行完t2再执行主函数
print(global_num)
结果:

如果注释掉 t1.join(), t2.join() 这两行,结果:(每一次都不一样)

472178
test1 1151965 <Thread(Thread-1, started 14300)>
test2 1279527 <Thread(Thread-2, started 4348)>

 

如果加上 t1.join(), t2.join() 这两行,结果:(每次都不一样)

主函数的结果和最后执行完的线程的结果一样

test1 1087118 <Thread(Thread-1, started 13744)>
test2 1320500 <Thread(Thread-2, started 19292)>
1320500
互斥锁

说到gil解释器锁,我们容易想到在多线程中共享全局变量的时候会有线程对全局变量进行的资源竞争,会对全局变量的修改产生不是我们想要的结果,而那个时候我们用到的是python中线程模块里面的互斥锁,哪样的话每次对全局变量进行操作的时候,只有一个线程能够拿到这个全局变量;看下面的代码:

import threading
import time
global_num = 0

lock = threading.Lock()

def test1():
    global global_num
    lock.acquire()
    for i in range(1000000):
        global_num += 1
    lock.release()
    print("test1", global_num)


def test2():
    global global_num
    lock.acquire()
    for i in range(1000000):
        global_num += 1
    lock.release()
    print("test2", global_num)

t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
start_time = time.time()

t1.start()
t2.start()
结果

互斥性:不可分割,要不不做,要做做完。

加上锁之后就变成了串行,加锁的范围尽量精确,范围要缩到最小

test1 1000000
test2 2000000

 

以上是关于并行,并发,多线程,GIL全局解释器锁的主要内容,如果未能解决你的问题,请参考以下文章

进程线程与GIL全局解释器锁详解

GIL全局解释器锁及协程

并发编程——GIL全局解释器锁死锁现象与递归锁信号量Event事件线程queue

python3多线程和GIL全局解释器所

GIL全局解释器锁

python 并发编程 多线程 GIL全局解释器锁基本概念