GIL全局解释器锁,多线程与多进程的效率,,协程,死锁(了解),递归锁(了解),信号锁(了解)。

本文详细介绍了GIL全局解释器锁的概念及其在Python解释器中的作用,探讨了多线程在计算密集型和IO密集型任务中的效率,并对比了多进程的优势。同时,文章提到了死锁现象和递归锁的基本概念,还讨论了信号量和线程队列在并发控制中的应用。最后,文章深入阐述了协程的原理、目的、优点及如何通过切换和保存状态来实现并发,特别提到了gevent库在协程并发中的使用。
摘要由CSDN通过智能技术生成

GIL全局解释器锁:

python解释器:

1.Cpython        C      ,    

2.Jpython        java  ,       

3.Ppython        Python  

1.GIL全局解释器锁:

 - 翻译: 在同一个进程下开启的多线程,同一时刻只能有一个线程执行,因为Cpython的内存管理不是线程安全。
 GIL全局解释器锁,本质上就是一把互斥锁,保证数据安全。
	单个进程下的多个线程无法实现并发,但能实现并发
结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。
GIL全局解释器锁
本质上就是一把互斥锁,保证数据安全。

2、GIL全局解释器锁的优缺点:

1.优点:
保证数据的安全
2.缺点:
单个进程下,开启多个线程,牺牲执行效率,无法实现并行,只能实现并发。

- IO密集型, 多线程
- 计算密集型,多进程

代码演示

import time
from threading import Thread, current_thread

number = 100

def task():
    global number
    number2 = number
    # time.sleep(1)
    number = number2 - 1
    print(number, current_thread().name)

for line in range(100):
    t = Thread(target=task)
    t.start()
    
#结果
99 Thread-1
98 Thread-2
.
.
.
1 Thread-99
0 Thread-100


import time
from threading import Thread

n = 100
def task():
    global n
    m = n
    time.sleep(3)
    n = m - 1

if __name__ == '__main__':
    list1 = []
    for line in range(10):
        t = Thread(target=task)
        t.start()
        list1.append(t)

    for t in list1:
        t.join()

    print(n)
    
# 结果:    
99

多线程的作用:

站在两个角度去看问题:

四个任务, 计算密集型, 每个任务需要10s:

单核:
开启进程
消耗资源过大
4个进程: 40s
开启线程
消耗资源远小于进程

- 4个线程: 40s
多核:
开启进程
并行执行,效率比较高
4个进程: 10s
开启线程
并发执行,执行效率低.
4个线程: 40s

四个任务, IO密集型, 每个任务需要10s:

单核:
开启进程
3消耗资源过大
34个进程: 40s
开启线程
消耗资源远小于进程
4个线程: 40s
多核:
开启进程
并行执行,效率小于多线程,因为遇到IO会立马切换CPU的执行权限
4个进程: 40s  +  开启进程消耗的额外时间
开启线程
并发执行,执行效率高于多进程

 4个线程: 40s
rom threading import Thread
from multiprocessing import Process
import os
import time

# 计算密集型
def work1():
    number = 0
    for line in range(100000000):
        number += 1

# IO密集型
def work2():
    time.sleep(1)

if __name__ == '__main__'

# IO密集型
    print(os.cpu_count())  # 6
    # 开始时间
    start_time = time.time()
    list1 = []
    for line in range(40):
        # p = Process(target=work2)  # 程序执行时间4.445072174072266
        p = Thread(target=work2)  # 程序执行时间1.009237289428711

        list1.append(p)
        p.start()

    for p in list1:
        p.join()
    end_time = time.time()

    print(f'程序执行时间{end_time - start_time}')


'''
在计算密集型的情况下:
    使用多进程
    
在IO密集型的情况下:
    使用多线程
    
高效执行多个进程,内多个IO密集型的程序:
    使用 多进程 + 多线程
'''
在计算密集型的情况下:
使用多进程   
在IO密集型的情况下:
使用多线程
高效执行多个进程,内多个IO密集型的程序:
使用 多进程 + 多线程
计算密集型: 多进程
计算密集型: 多进程
    假设100份原材料同时到达工厂,聘请100个人同时制造,效率最高
IO密集型: 多线程
假设100份原材料,只有40份了,其他还在路上,聘请40个人同时制造。

死锁现象

from threading import Lock,Thread,current_thread
import time

mutex_a = Lock()
mutex_b = Lock()

class MyThread(thread):
    #线程执行任务
    def run(self)
    	self.func1()
        self.func2()
       def func1(self):
        mutex_a.acquire()
        print(f'用户{self.name}强到锁a')
        mutex_b.acquire()
        print(f'用户{self.name}抢到锁b')
        mutex_b.release()
        print(f'用户{self.name}释放锁b')
        mutex_a.release()
        print(f'用户{self}释放锁a')
        
   def func2(self):
    mutex_b.acquire()
    print(f'用户{self.name}抢到锁b')
    # IO操作
    rime.sleep(1)
    
    mutex_a.acquire()
    print(f'用户{self.name}抢到锁a')
    mutex_a.release()
    print(f'用户{self.name}释放锁a')
    mutex_b.release()
    print(f'用户{self.name}释放锁b')
    
for line in range(10):
	t = MyThread()
    t.start()
    
    注意:
    	锁不能乱用

递归锁(了解)

用于解决死锁问题。

RLock:比喻成万能钥匙,可以提供给多个人用,但是第一个使用的时候,会对该锁做一个引用计数.只有引用计数为0,才能真正释放让另一个去使用。

from threading import RLock, Thread, Lock
import time

mutex_a = mutex_b = Lock()

class MyThread(Thread):

    # 线程执行任务
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutex_a.acquire()
        # print(f'用户{current_thread().name}抢到锁a')
        print(f'用户{self.name}抢到锁a')
        mutex_b.acquire()
        print(f'用户{self.name}抢到锁b')
        mutex_b.release()
        print(f'用户{self.name}释放锁b')
        mutex_a.release()
        print(f'用户{self.name}释放锁a')

    def func2(self):
        mutex_b.acquire()
        print(f'用户{self.name}抢到锁b')
        # IO操作
        time.sleep(1)
        mutex_a.acquire()
        print(f'用户{self.name}抢到锁a')
        mutex_a.release()
        print(f'用户{self.name}释放锁a')
        mutex_b.release()
        print(f'用户{self.name}释放锁b')

for line in range(10):
    t = MyThread()
    t.start()

信号量(了解):

互斥锁:

 比喻成一个家用马桶.        同一时间只能让一个人去使用    

信号量:

比喻成公厕多个马桶.        同一时间可以让多个人去使用
from threading import Semaphore, Lock
from threading import current_thread
from threading import Thread
import time

sm = Semaphore(5)  # 5个马桶
mutex = Lock()  # 5个马桶

def task():
    sm.acquire()
    print(f'{current_thread().name}执行任务')
    time.sleep(1)
    sm.release()
    # mutex.release()

for line in range(20):
    t = Thread(target=task)
    t.start()

线程队列

线程Q(了解级别1): 线程队列 面试会问: FIFO

FIFO队列:
先进先出  
LIFO队列:
后进先出
优先级队列:
根据参数内,数字的大小进行分级,数字值越小,优先级越高
import queue

# 普通的线程队列: 先进先出
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())  # 1

# LIFO队列: 后进先出
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())  # 3

基于TCP协议套接字,服务端实现接收客户端的连接并发。

客户端

# client 客户端

import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 8866)
)
while True:
    cmd = input('hello>>: ').strip()
    if not cmd:
        continue
    if cmd =='q':
        break
    client.send(cmd.encode('utf-8'))
    data = client.recv(1024)

    print(data)

client.close()

服务端

# server服务端

import socket
from threading import Thread
import threading

server = socket.socket()
server.bind(
    ('127.0.0.1', 8866)
)

server.listen(2)

def action(conn):
    while True:
        data = conn.recv(1024)
        print(data)
        conn.send(data.upper())

if __name__ == '__main__':

    while True:
        conn, addr = server.accept()
        p = threading.Thread(target=action, args=(conn,))
        p.start()
server.close()

3.协程

进程:

资源单位

线程:

执行单位

协程:

在单线程下实现并发。

在IO密集型的情况下,使用协程能提高最高效率。

注意:

协程不是操作系统资源,他是程序取的名字,为让单线程能实现并发。

协程的目的:

 操作系统:躲到技术,切换+保存状态

 躲到技术,切换+保存状态
- 手动实现 "遇到IO切换 + 保存状态" 去欺骗操作系统,让操作系统误以为没有IO操作,将CPU的执行权限给你。
 1.遇到 IO

 2.CPU执行时间长

协程意义:

通过手动模拟操作系统‘多道技术’,实现 切换+保存。

 1.手动实现 遇到 IO却换,欺骗操作系统误以没有 IO操作。

 单线程 遇到 IO ,切换 + 保存状态。

 单线程 计算密集型。来回切换 + 保存状态,反而效果更低

优点:

在 IO密集型的情况下,会提高效率。

缺点:

若在计算密集型的情况下,来回切换,反而效率更低。

如何实现协程:切换 + 保存状态。

基于 yield 并发执行(验证计算密集型的情况下,反而效率更低)

并发:切换

# 串行执行(串行比协程并发性更高)
import time
def func1():
    for i in range(10000000):
        i+1
def func2():
    for i in range(10000000):
        i+1

start = time.time()
func1()
func2()
stop = time.time()
print(stop-start)   #1.0149040222167969


# 验证计算密集型的情况下效果更低  #  1.2908563613891602   还比如串行
# 基于yield并发执行
import time
def func1():
    while True:
        10000000+1
        yield
def func2():
    # g 生成器对象
    g = func1()
    for i in range(10000000):
        # time.sleep(100)# 模拟 IO,yield 并不会捕捉到并自动切换。
        i+1
        next(g)   #每次执行next相当与切换到func1下面。

start = time.time()
func2()
stop = time.time()
print(stop-start)  # 1.2908563613891602

# 两个都是计算密集型,但是通过手动实现 切换+保存执行效率会低一些(用的秒长一些)。

gevent

是一个第三方模块,可以帮你监听 IO操作,并切换。

需要下载:pip3 install gevent

使用gevent目的:

为了实现单行线程下,实现遇到 IO, 保存状态 +切换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值