进程,线程和协程;为什么有了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个类就属于同一个类型
标签:__,协程,多态性,worker,多态,互斥,线程,进程,gil
From: https://www.cnblogs.com/10086upup/p/17603978.html