GIL、协程

一、GIL全局解释器锁 global interpreter lock

1、GIL是一个互斥锁:保证数据的安全(以牺牲效率来换取数据的安全),阻止同一个进程内多个线程同时执行(不能并行但是能够实现并发)

2、GIL全局解释器存在的原因是因为Cpython解释器的内存管理不是线程安全的

3、CIL是一个互斥锁,是加在Cpython解释器上的,同一进程内的所有线程都需要先抢到GIL锁,才能执行解释器代码

4、GIL优缺点:

    优点:保证Cpython解释器内存管理的线程安全

    缺点:同一进程内所有线程同一时刻只能有一个执行,也就是Cpython解释器的多线程无法实现并行

5、有了GIL存在,同一进程内所有线程同一时刻只能有一个执行

6、进程可以利用多核,但是开销大 python多线程开销小,但无法利用多核优势 --> 是不是意为python无用? --> 当然不是

分析
①cpu是用来计算的
②多cpu,可以有多个核并行完成计算,提升计算效率
③每一个cpu遇到io阻塞时,需要等待,多核对提升io无用
结论:对于计算来说,cpu越多越好。对于io来说,再多cpu也无用
案例:
四个任务玩出并发效果
方案一:开四个进程
方案二:一个进程下,开四个线程

单核:计算密集型:--> 方案一开销大  -->方案二胜
    io密集型:--> 方案一开销大  -->方案二胜
    
多核:计算密集型:--> 多核提升计算 --> 方案一胜
    io密集型:--> 再多核也无用 --> 方案二胜
    
总结:#结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,
甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的
问题:python多线程是不是就没有用了呢?
四个任务:计算密集的任务   每个任务耗时10s
单核情况下:
    多线程好一点,消耗的资源少一点
多核情况下:
    开四个进程:10s多一点
    开四个线程:40s多一点

四个任务:IO密集的任务   每个任务io 10s
单核情况下:
    多线程好一点
多核情况下: 、
    多线程好一点 
多线程和多进程都有自己的优点,要根据项目需求合理选择

7、多线程性能测试

①计算密集型

from multiprocessing import Process
from threading import Thread
import os, time

def work():
    res = 0
    for i in range(100):
        res *= i

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为4核
    start = time.time()
    for i in range(4):
        p = Process(target=work)      # 耗时0.7540433406829834
        # p=Thread(target=work)      # 耗时0.0030002593994140625
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))

②io密集型

from multiprocessing import Process
from threading import Thread
import threading
import os, time

def work():
    time.sleep(2)

if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为4核
    start = time.time()
    for i in range(100):
        # p = Process(target=work)      # 耗时11.416653156280518
        p = Thread(target=work)          # 耗时2.0711185932159424
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))

 

二、GIL与普通锁的对比

1、对于不同的数据,要想保证安全,需要加不同的锁处理

2、GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程 保证的是同一个进程下多个线程之间的安全

3、要想保证数据的安全,要自己加不同的锁

from threading import Thread, Lock
import time

# mutex 互斥
mutex = Lock()
n = 100

def task():
    global n

    mutex.acquire()
    tmp = n
    time.sleep(0.1)
    n = tmp - 1
    mutex.release()

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)

for t in t_list:
    t.join()
print(n)
# 结果为 0

 

三、死锁与递归锁(了解)

from threading import Thread, RLock
import time

自定义锁一次acquire必须对应一次release,不能连续acquire
递归锁可以连续的acquire,每acquire一次计数加一:针对的是第一个抢到我的人


mutexA = mutexB = RLock()  # 抢锁之后会有一个计数 抢一次计数加一 针对的是第一个抢到我的人

class MyThead(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print('%s 抢到A锁了' % self.name)
        mutexB.acquire()
        print('%s 抢到B锁了' % self.name)
        mutexB.release()
        print('%s 释放了B锁' % self.name)
        mutexA.release()
        print('%s 释放了A锁' % self.name)

    def func2(self):
        mutexB.acquire()
        print('%s 抢到了B锁' % self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s 抢到A锁了' % self.name)
        mutexA.release()
        print('%s 释放了A锁' % self.name)
        mutexB.release()
        print('%s 释放了B锁' % self.name)

for i in range(100):
    t = MyThead()
    t.start()

 

四、信号量Semaphore(了解)

# Semaphore 信号量  (公共卫生间)

from threading import Thread, Semaphore
import time
import random

sm = Semaphore(5)   # 五个厕所五把锁
          # 跟你普通的互斥锁区别在于,普通的互斥锁是独立卫生间,所有人抢一把锁
          # 信号量 公共卫生间 有多个坑,所有人抢多把锁
def task(name):
    sm.acquire()
    print('%s正在蹲坑' % name)
    time.sleep(random.randint(1, 3))  # # 模拟蹲坑耗时
    sm.release()

if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task, args=('特种兵%s号' % i,))
        t.start()

 

五、event事件(了解)

from threading import Thread, Event
import time

event = Event()   # 设置一个事件实例
def light():
    print('红灯亮着')
    time.sleep(2)
    event.set()   # 解除阻塞,给我的event发了一个信号
    print('绿灯亮着')

def car(i):
    print('%s 正在等红灯' % i)
    event.wait()   # 阻塞
    print('%s 开车走了' % i)
t1 = Thread(target=light)
t1.start()

for i in range(10):
    t = Thread(target=car, args=(i,))
    t.start()

 

六、线程queue

import queue

1、普通q
q = queue.Queue(4)
q.put(1)
q.put(2)
q.put(3)
q.put(4)

print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 结果为
1
2
3
4


2、先进后出q
q = queue.LifoQueue(4)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 结果为
4
3
2
1

3、优先级q 最小->>>>最大 q = queue.PriorityQueue() q.put((10, 'a')) q.put((-20, 'b')) q.put((3000, 'c')) q.put((100, 'd')) print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 结果为 (-20, 'b') (10, 'a') (100, 'd') (3000, 'c')

 

posted @ 2019-05-08 16:11  TianShu  Views(205)  Comments(0Edit  收藏  举报