进程,线程和协程;为什么有了GIL锁还要互斥锁;多态和多态性;鸭子类型

发布时间 2023-08-03 17:35:16作者: 雀雀飞了

进程,线程和协程;为什么有了GIL锁还要互斥锁;多态和多态性;鸭子类型

为什么有了GIL锁还要互斥锁

1.GIL本身就是一个大的互斥锁
2.同一个进程下资源是共享的,也就是说多条线程可以操作同一个变量
3.多个线程可以操作同一个变量就会出现数据安全问题
4.临界区:指一段代码或一段程序片段,需要在同一时间只能被一个线程执行,所以多个线程操作临界区,会出现并发安全问题

# 错误的理解
-有了gil锁,同一时刻,只有一个线程执行,临界区代码,不也只有一个线程在执行吗?只有一个线程在执行,临界区就是安全的,不会出现数据错乱,所以没有必要再加互斥锁了

# 正确的解释
-gil锁释放不是我们控制的,比如在执行临界区中间,释放了,就会数据错乱问题

# 什么时候会释放gil锁?
1 线程遇到io
2 时间片轮转,时间片到了,就会自动切换

# 小案例解释
进程中有个变量a=0
    
    临界区:a+=1
    
    线程1要计算: a+=1  
    	1 线程1 拿到gil  
        2 读取a=0
        3 假设时间片到了,释放gil,释放cpu
      	4 等待下次被调度执行
        10 轮到它了,获取gil锁
        11 继续往下执行:计算a+1
        12 把结果赋值给a ,a=1
    	13 释放gil锁
     线程2要计算: a+=1 
        5 线程2获得了gil锁
        6 读取a=0
        7 计算a+1
        8 把结果赋值给a ,a=1
        9 释放gil锁
        
# 互斥锁保证数据安全
    a=0
    线程1要计算: a+=1  
    	1 线程1 拿到gil  
        # 加锁
        2 读取a=0
        3 假设时间片到了,释放gil,释放cpu
      	4 等待下次被调度执行
        
        7 轮到它了,获取gil锁
        8 继续往下执行:计算a+1
        9 把结果赋值给a ,a=1
    	10 释放gil锁
     线程2要计算: a+=1 
        5 线程2获得了gil锁
        #获取锁,获取不到
        6 释放gil锁
        11 获得gil锁
        
        #加锁
        12 读取a=0
        13 计算a+1
        14 把结果赋值给a ,a=1
        15 释放锁
        16 释放gil锁
    
    # gil锁并不锁住临界区,临界区需要我们自己用互斥锁加锁
    

进程,线程和协程

进程、线程和协程是并发编程中常见的三种并发执行方式。它们在代码中的体现略有不同:

# 1. 进程:
- 进程是操作系统分配资源的最小单位。一个应用程序运行起来至少有一个进程,进程管理器中就可以看到一个个的进程。每个进程都有自己独立的地址空间,相互之间不会干扰。
# 开启多进程两种方式
-1 写一个类,继承Process,重写类的run方法---》实例化得到对象,对象.start 开启了进程
-2 通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
import multiprocessing

def worker():
    print("This is a worker process.")

if __name__ == "__main__":
    process = multiprocessing.Process(target=worker)
    process.start()
    process.join()


# 2. 线程:
- 线程是操作系统调度的最小单位。多个线程共享同一个进程的地址空间,可以直接访问共享资源,一个进程下至少有一个线程。
# 开启线程两种方式
-1 写一个类,继承Thread,重写类的run方法---》实例化得到对象,对象.start 开启了进程
-2 通过Thread类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程

import threading

def worker():
    print("This is a worker thread.")

if __name__ == "__main__":
    thread = threading.Thread(target=worker)
    thread.start()
    thread.join()

class PrintNumbersThread(threading.Thread):
  def run(self):
    for i in range(1,11):
      print(i)
      
t = PrintNumbersThread()
t.start()
    

# 3. 协程:
- 协程是一种轻量级的线程,单线程下的并发。协程是程序层面的任务切换,而不是操作系统的抢占式调度。
# 开启协程两种方式
- 早期:借助于第三方gevent,基于greenlet写的
- async 和 await 关键字,不借助于第三方,开启协程asyncio包
		- 必须写在一个函数前, async def task()----》这个函数执行的结果是协程函数
		-await 只要是io操作的代码,前面必须加 await

import asyncio

async def worker():
  	a+=2
  	await 遇到io
    a+=2=
    print("This is a worker coroutine.")

if __name__ == "__main__":
    asyncio.run(worker())


你在哪里用过

-我一般遇到计算密集型的操作,我会开多进程,io密集型的操作,我一般开多线程
-闲来无事,爬别人数据,喜欢开多线程,爬虫io居多
-程序中,异步做一件事情,也可以开多线程
        比如一个视图函数,异步的吧数据写的文件中
        异步的发送钉钉通知
        异步的发送邮件
-但实际上,在项目中,不需要我们开启进程线程,可以借助于第三方的框架比如celery就可以做异步的操作
        而celery的worker,就是进程线程架构
-django框架,是支持并发,我们没有开启多进程和多线程,但是符合uwsgi的web服务器在进入djagno框架之前,开启了进程和线程来执行视图函数


# 进程间通信需要使用管道,消息队列实现
# 线程间通信用什么?
# 协程会出现并发安全的问题吗?

什么是鸭子类型

1 鸭子类型是python语言面向对象中的一个概念

# 面向对象三大特性
继承封装和多态

# 多态和多态性
-多态指的是同一类食物的多种形态,现实生活中水可以是冰、水、水蒸气,动物可以是人、狗、猪;程序中,一个Animal类可以是子类Human、Dog、Pig。
-多态性是不考虑对象具体类型的情况下使用对象,在程序中
		-len()内置函数---》传参数:字符串对象,列表对象,字典对象
-为什么能这样用?就是因为多态的存在
    -字符串,列表,字典----》属于同一类事物---》有长度的这一类事物

# 鸭子类型
-走路像鸭子,说话像鸭子,我们就可以叫它叫鸭子
-解释:鸭子类型是python面向对象中描述接口的一个概念,区分与其他编程语言,
-比如java:实现接口,必须显示地继承一个接口
-而python:实现接口,遵循鸭子类型,不需要显示地继承一个接口(类),只要类中有对应的属性跟方法,我们就称这几个类的对象为同一种类型

举例:
Python 的迭代器协议并不要求对象继承特定的接口或基类,只要对象实现了 __iter__() 和 __next__() 方法,它就可以被视为一个迭代器并可以用于 for 循环中。这符合了鸭子类型的思想,关注对象的行为而不是具体的类型。
或者说没有继承相同的父类,但是具有相同的方法,这2个类就属于同一个类型