Python基础之全局锁GIL、协程

发布时间 2023-07-11 17:37:34作者: Way*yy

全局锁GIL

"""虽然Python解释器可以运行多线程,但是任何同一时间下只有一个线程在解释器中运行"""

"对于Python解释器的访问由全局解释器锁(GIL)控制,正是这个锁保证了同一时间内只会有一个线程的运行"
1、为什么要保持同一时间内只让一条线程运行呢?
	假如说现在有两条线程,线程1负责清理a = 1 的垃圾数据,当线程1正在清理这条数据的时候,恰好线程二又调用了这个数据,那么这两个线程就会产生冲突
    
2、Python解释器的版本:
	CPython  IPython PyPy Jpython
3、GIL锁只存在于CPython解释器中
Python在设计之初,就在Python解释器之上加了一把锁(GIL锁),目的是为了同一时刻内只会有一个线程执行,不能同一时间内有多个线程执行,如果说开了多线程,线程想要执行必须先拿到这把锁
"""多线程:同一时间内多个线程运行,只会有一个线程拿到这把锁,其他线程处于等待的状态,其实GIL锁的本质就是互斥锁"""

###################################################################
1. python有GIL锁的原因,同一个进程下多个线程实际上同一时刻,只有一个线程在执行
2. 只有在python上开进程用的多,其他语言一般不开多进程,只开多线程就够了
3. cpython解释器开多线程不能利用多核优势,只有开多进程才能利用多核优势,其他语言不存在这个问题
4. 8核cpu电脑,充分利用起我这个8核,至少起8个线程,8条线程全是计算--->计算机cpu使用率是100%,
5. 如果不存在GIL锁,一个进程下,开启8个线程,它就能够充分利用cpu资源,跑满cpu
6. cpython解释器中好多代码,模块都是基于GIL锁机制写起来的,改不了了---》我们不能有8个核,但我现在只能用1核,----》开启多进程---》每个进程下开启的线程,可以被多个cpu调度执行
7. cpython解释器:io密集型使用多线程,计算密集型使用多进程
	# -io密集型,遇到io操作会切换cpu,假设你开了8个线程,8个线程都有io操作---》io操作不消耗cpu---》一段时间内看上去,其实8个线程都执行了, 选多线程好一些
   
# -计算密集型,消耗cpu,如果开了8个线程,第一个线程会一直占着cpu,而不会调度到其他线程执行,其他7个线程根本没执行,所以我们开8个进程,每个进程有一个线程,8个进程下的线程会被8个cpu执行,从而效率高
'''计算密集型选多进程好一些,在其他语言中,都是选择多线程,而不选择多进程.'''

互斥锁

版本一:
    from threading import Thread, Lock
    import time

    n = 10


    def index():
        global n
        temp = n
        time.sleep(1)
        n = temp - 1


    if __name__ == '__main__':
        t_list = []
        for i in range(10):
            t = Thread(target=index)
            t.start()
            t_list.append(t)

        for j in t_list:
            j.join()

        print(n)  # 9
# 为什么n这时侯等于9呢?
	我们想的是第一个线程拿到n为10,第二个线程拿到的n的值为9....以此类推,第十个线程的值为0,但是现在n的值为9这是为什么?
	因为线程的执行速度太快了,当第一个线程走到sleep的时候遇到了I/O,随后第二个线程到第十个线程,拿到的n的值都为10,所以n的值没有发生改变
    
解决办法:加锁
	from threading import Thread, Lock
    import time

    n = 10


    def index(lock):
        lock.acquire()
        global n
        temp = n
        time.sleep(1)
        n = temp - 1
        lock.release()


    if __name__ == '__main__':
        lock = Lock()
        t_list = []
        for i in range(10):
            t = Thread(target=index, args=(lock,))
            t.start()
            t_list.append(t)

        for j in t_list:
            j.join()

        print(n)  # 0

线程队列

#  进程之间的数据是隔离的,所以,我们使用队列来实现了进程之间的通信

#  线程之间的数据是共享的,那为什么还要使用队列呢?

"""
	队列的底层其实还是:管道 + 锁
	锁就是为了保证数据的安全
	线程的内部使用队列,也是为了保证线程里的数据安全
"""

# 进程Queue用于父进程与子进程(或同一父进程中多个子进程)间数据传递
# python自己的多个进程间交换数据或者与其他语言(如Java)进程queue就无能为力

# queue.Queue 的缺点是它的实现涉及到多个锁和条件变量,因此可能会影响性能和内存效率。
'''只要加锁,必会影响性能和效率!但是好处就是保证数据的安全'''

#  线程的队列使用
#  先进先出
import queue

q = queue.Queue()
q.put('hello')
q.put('hello 1')
q.put('hello 2')

print(q.get())
print(q.get())
print(q.get())
"""
    hello
    hello 1
    hello 2
"""

#  先进后出
import queue

# Lifo:last in first out
q = queue.LifoQueue()
q.put('hello')
q.put('hello 1')
q.put('hello 2')

print(q.get())
print(q.get())
print(q.get())

"""
hello 2
hello 1
hello
"""

# 队列的优先级:(数字越小优先级越高,优先级高的优先出队)
import queue

q = queue.PriorityQueue()
q.put((3, "a"))
q.put((1, "b"))
q.put((2, "c"))

print(q.get())
print(q.get())
print(q.get())

"""
(1, 'b')
(2, 'c')
(3, 'a')
"""

进程池和线程池

池子:容器,盛放多个元素值
进程池:存放多个进程的
线程池:存放多个线程的

"""
	进程池:提前定义一个池子,里面放很多个进程,只需要往池子里面丢任务即可,由这个池子里面的任意一个进程来执行任务.
	
	线程池:提前定义一个池子,里面放很多个线程,只需要往池子里面丢任务即可,由这个池子里面的任意一个线程来执行任务.
"""

# 一个是线程池一个是进程池
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


# 任务一
def index(n, m):
    print("from >>>> index")
    return n + m


# 回调函数
# 回调函数的作用就是拿到任务的返回值
def callback(res):
    print(res.result())


if __name__ == '__main__':
    # 开一个线程池,里面存有5个进程
    # 当我的任务数量大于我线程数量的时候,假如说我的任务有10个但是我的进程只有5个。那么,我的线程还会先执行前五个任务,其余的五个任务只能先等着,而后执行完任务的线程会重新回到线程池里面,执行后面的任务
    """进程池同理"""
    pool = ThreadPoolExecutor(5)
    pool.submit(index, n=10, m=20).add_done_callback(callback)

    pool.shutdown()
    print(1234)

线程池爬取网页

import requests
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def get_page(url):
    res = requests.get(url)  # 爬取网页
    name = url.rsplit('/')[-1] + '.html'
    return {'name': name, 'text': res.content}


def call_back(fut):
    print(fut.result()['name'])
    with open(fut.result()['name'], 'wb') as f:
        f.write(fut.result()['text'])


if __name__ == '__main__':
    pool = ThreadPoolExecutor(2)
    urls = ['http://www.baidu.com', 'http://www.cnblogs.com', 'http://www.taobao.com']
    for url in urls:
        pool.submit(get_page, url).add_done_callback(call_back)
        
谈谈:解决高并发问题
linux:负载均衡

了解协程

进程:进程解决高并发问题

线程:解决高并发问题

协程:它是单线程下的并发,它是程序员级别的,我们来控制如何切换,什么时候切换
进程和线程是操作系统来控制的,我们控制不了

协程是一种用户态(程序员)的轻量级线程,即协程是由用户程序自己控制调度的。

进程的开销 >>>>>> 线程的开销 >>>>>> 协程的开销

# 协程的使用需要借助于第三方模块gevent模块
必须先安装: pip install gevent

开启协程

import gevent

import time


def eat(name):
    print('%s eat 1' % name)
    # gevent.sleep(2)  # 模拟吃的过程
    # time但是它不会切
    gevent.sleep(2)  # 模拟吃的过程
    print('%s eat 2' % name)


def play(name):
    print('%s play 1' % name)
    gevent.sleep(1)  # 模拟玩的过程
    print('%s play 2' % name)


start = time.time()
g1 = gevent.spawn(eat, 'lqz')
g2 = gevent.spawn(play, name='lqz')
g1.join()  
g2.join()
# 或者gevent.joinall([g1,g2])
print('主')
print("time:", time.time() - start)

#  当程序遇到I/O就会跳到另一个进程,执行另一个进程,若第二个进程又有I/O就会再跳回到第一个进程,就这样一直反复横跳,只要没有I/O然后执行完所有的代码体