36 线程2....GIL

1.GIL全局解释器锁(******) 
  GIL:global interpreter lock,是一个互斥锁,存在于Cpython解释器内:
    保证数据的安全(以牺牲效率来换取数据的安全)
    阻止同一个进程内多个线程同时执行(不能并行但是能够实现并发)

    线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行
36 线程2....GIL
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
PIC定义

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

Cpython解释器的内存管理:垃圾回收机制    

1)引用计数    

2)标记清除    

3)分代回收  

垃圾回收机制也是一个任务,和普通的代码不是串行运行,如果是串行会明显有卡顿
垃圾回收到底是开进程还是开线程?肯定是线程,所以想运行也必须要拿到python解释器
假设能够并行,会出现什么情况?一个线程刚好要造一个a=1的绑定关系之前,这个垃圾线程来扫描,矛盾点就来了,谁成功都不对!
也就意味着在Cpython解释器上有一把GIL全局解释器锁   

所以在Cpython解释器中,同一个进程下:

  多个线程不能实现并行但是能够实现并发

  多个进程下的线程能够实现并行 

 

问题:python多线程是不是就没有用了呢?

 1)四个任务:计算密集的任务,每个任务耗时10s

单核情况下:    

  多线程好一点,消耗的资源少一点

多核情况下:    

  开四个进程:10s多一点    

  开四个线程:40s多一点

36 线程2....GIL
from multiprocessing import Process
from threading import Thread
import os, time


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


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为8核
    start = time.time()
    for i in range(8):
        p=Process(target=work) # 耗时9.252728939056396
        p = Thread(target=work)  # 耗时35.369622230529785
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))
计算密集型

2)四个任务:IO密集的任务,每个任务io 10s

单核情况下:    

  多线程好一点

多核情况下:    

  多线程好一点

36 线程2....GIL
from multiprocessing import Process
from threading import Thread
import os, time


def work():
    time.sleep(2)


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为8核
    start = time.time()
    for i in range(600):
        p = Process(target=work)  # 耗时4.699530839920044
        # p=Thread(target=work) # 耗时2.054128885269165
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))
IO密集型

  >>> 多线程和多进程都有自己的优点,要根据项目需求合理选择 


2.GIL全局解释器锁与普通的互斥锁的区别
  1)对于不同的数据,要想保证安全,需要加不同的锁处理
  2)GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程
  3)保证的是同一个进程下多个线程之间的安全
from threading import Thread, Lock
import time

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)

3.死锁与递归锁(了解)
  自定义锁一次acquire必须对应一次release,不能连续acquire
  递归锁可以连续的acquire,每acquire一次计数加一:针对的是第一个抢到锁的人
from threading import Thread,Lock,RLock
import time

# mutexA = Lock()
# mutexB = Lock() # 形成死锁
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()

4.信号量(了解)
  普通互斥锁:普通的互斥锁是独立卫生间,所有人抢一把锁
  信号量:公共卫生间,有多个坑,所有人抢多把锁
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, 5))
    sm.release()


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

5.event时间
  一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
from threading import Event, Thread
import time

event = Event()


def light():
    print('红灯亮着!')
    time.sleep(3)
    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()

6.线程queue
同一个进程下的线程数据都是共享的为什么还要用queue?
  queue本身自带锁的功能,能够保证数据的安全
  1)Queue 普通:先进先出
  2)LifoQueue 先进后出:last in first out → 堆栈
  3)PriorityQueue 优先级:用数字表示,数字越小优先级越高
import queue


q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())

q = queue.LifoQueue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print(q.get())



q = queue.PriorityQueue()
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())
print(q.get())
print(q.get())


>>>:
  1
  2
  3

  4

  (-1, 'b')
  (10, 'a')
  (100, 'c')

7.进程池线程池(******) 
8.如何使用进程池线程池(******)
上一篇:GIL


下一篇:day37