Python——进程、线程、协程详解

1.进程
2.线程
3.协程

进程

1.进程定义

  1. 进程是资源分配最小单位

  2. 当一个可执行程序被系统执行(分配内存等资源)就变成了一个进程

    进程定义拓展回答内容

  • 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
  • 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
  • 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
  • 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
  • 进程之间有自己独立的内存,各进程之间不能相互访问
  • 创建一个新线程很简单,创建新进程需要对父进程进行复制

2.有了进程为什么还要线程?

  1. 进程优点:
    提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率
  2. 进程的两个重要缺点
    a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
    b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。
    c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息
    d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀
    e. 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀

3.进程间互相访问数据的四种方法

注:不同进程间内存是不共享的,所以互相之间不能访问对方数据

1: 利用Queues实现父进程到子进程(或子进程间)的数据传递
2: 使用管道pipe实现两个进程间数据传递
3: Managers实现很多进程间数据共享
4:借助redis中间件进行数据共享
Queue(队列)
from multiprocessing import Process, Queue
def start(q):
    q.put( 'hello')

if __name__ == '__main__':
    q = Queue()
    p = Process(target=start, args=(q,))
    p.start()
    print(q.get())
    p.join()

Manager(实现了进程间真正的数据共享)

from multiprocessing import Process, Manager
def f(dic, list,i):
    dic['1'] = 1
    dic['2'] = 2
    dic['3'] = 3
    list.append(i)

if __name__ == '__main__':
    manager = Manager()
    dic = manager.dict()#通过manager生成一个字典
    list = manager.list(range(5))#通过manager生成一个列表
    p_list = []
    for i in range(10):
        p = Process(target=f, args=(dic, list,i))
        p.start()
        p_list.append(p)
    for res in p_list:
        res.join()

    print(dic)
    print(list)
#执行结果
'''
{'2': 2, '3': 3, '1': 1}
[0, 1, 2, 3, 4, 1, 9, 2, 5, 3, 7, 6, 0, 8, 4]
'''

4.进程和程序的区别

  1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体
  2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资源或事件而被处于等待状态,因完成任务而被撤消
  3. 进程是系统进行资源分配和调度的一个独立单位
  4. 一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
  5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程

5.进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
1、apply(同步)
2、apply_async(异步)

from  multiprocessing import Process,Pool
import time,os
def foo(i):
    time.sleep(2)
    print("in the process",os.getpid()) #打印子进程的pid
    return i+100
 
def call(arg):
    print('-->exec done:',arg,os.getpid())
 
if __name__ == '__main__':
    pool = Pool(3)                      #进程池最多允许5个进程放入进程池
    print("主进程pid:",os.getpid())     #打印父进程的pid
    for i in range(10):
       #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数
        pool.apply_async(func=foo, args=(i,),callback=call)
        #用法2 串行 启动进程不在用Process而是直接用pool.apply()
        # pool.apply(func=foo, args=(i,))
    print('end')
    pool.close()    #关闭pool
    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

线程

线程的定义
①.线程是操作系统的调度的最小单位
②.线程被包含在进程之中,是进程中的实际运行单位
③.进程本身是无法自己运行的,要操作CPU,必须创建一个线程,线程是一系列指令的集合

线程的注意点

①.线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运行单位
②.一条线程是指进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
③.无论启动多少个线程,有多少个CPU,python在执行的时候在同一时刻只允许一个线程运行
④.所有在同一进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
⑤.同一进程中的各线程可以相互访问资源,线程可以操作 同进程中的其他线程,但进程仅能操作子进程
⑥.两个进程想通信,必须要通过一个中间代理
⑦.对主线程修改可能会影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存

线程和进程的区别
①.进程包含线程
②.线程共享内存空间
③.进程内存是独立的(不可相互访问)
④进程可以生成子进程,子进程之间不能互相访问
⑤.线程可以帮助应用程序同时做几件事情

for循环同时启动多个线程

import threading
import time

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(10):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()

join()实现所有线程都执行结束后再执行主线程

import threading
import time
start_time = time.time()

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
t_objs = []    #将进程实例对象存储在这个列表中
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.start()          #启动一个线程,程序不会阻塞
    t_objs.append(t)
print(threading.active_count())    #打印当前活跃进程数量
for t in t_objs: #利用for循环等待上面50个进程全部结束
    t.join()     #阻塞某个程序
print(threading.current_thread())    #打印执行这个命令进程

print("----------------all threads has finished.....")
print(threading.active_count())
print('cost time:',time.time() - start_time)

setDaemon()守护线程,主线程退出时,需要子线程随主线程退出

import threading
import time
start_time = time.time()

def sayhi(num): #定义每个线程要运行的函数
    print("running on number:%s" %num)
    time.sleep(3)
for i in range(50):
    t = threading.Thread(target=sayhi,args=('t-%s'%i,))
    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置
    t.start()          #启动一个线程,程序不会阻塞
print('cost time:',time.time() - start_time)

GIL全局解释器锁:保证同一时间仅有一个线程对资源操作

作用:在一个进程内,同一时刻只能有一个线程执行

线程锁

①.当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作
②.可以解决还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱的问题

import time
import threading
lock = threading.Lock()          #1 生成全局锁
def addNum():
    global num                  #2 在每个线程中都获取这个全局变量
    print('--get num:',num )
    time.sleep(1)
    lock.acquire()              #3 修改数据前加锁
    num  -= 1                   #4 对此公共变量进行-1操作
    lock.release()              #5 修改后释放

Semaphore(信号量)
①.互斥锁同时只允许一个线程更改数据,而semaphore是同时允许一定数量的线程更改数据
②.作用就是同一时刻允许运行的线程数量

线程实现并发

import requests
from concurrent.futures import ThreadPoolExecutor

def fetch_request(url):
    result = requests.get(url)
    print(result.text)

url_list = [
    'https://www.baidu.com',
    'https://www.google.com/',         #google页面会卡住,知道页面超时后这个进程才结束
    'http://dig.chouti.com/',          #chouti页面内容会直接返回,不会等待Google页面的返回
]

pool = ThreadPoolExecutor(10)            # 创建一个线程池,最多开10个线程
for url in url_list:
    pool.submit(fetch_request,url)       # 去线程池中获取一个线程,线程去执行fetch_request方法

pool.shutdown(True)  

协程

协程的定义
①.协程又称微线程,是一种用户状态的轻量级线程
②.线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈
③.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
④.协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
⑤.协程主要作用是在单线程的条件下实现并发的效果,但实际上还是串行

协程的缺点(无法利用多核资源)
协程的本质是个单线程,不能同时将单个CPU的多个核上,协程需要和进程配合才能运行在多CPU上,线程阻塞会阻塞到整个程序

协程为何能处理大并发
①.greeenlet遇到I/O手动切换
协程遇到I/O操作就切换,其实Gevent模块仅仅是对greenlet的封装,将I/O的手动切换变成自动切换
②.Gevent遇到I/O自动切换
Gevent是一个第三方库,可以通过gevent实现并发同步或异步编程,Gevent原理是只要是遇到I/O操作就自动切换下一个协程

使用协程处理并发

注意:Gevent只起一个线程,当请求发出去后,gevent就不管,永远只有一个线程工作,谁先回来先处理

import gevent
import requests
from gevent import monkey

monkey.patch_all()

# 这些请求谁先回来就先处理谁
def fetch_async(method, url, req_kwargs):
    response = requests.request(method=method, url=url, **req_kwargs)
    print(response.url, response.content)

# ##### 发送请求 #####
gevent.joinall([
    gevent.spawn(fetch_async, method='get', url='https://www.python.org/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://www.google.com/', req_kwargs={}),
    gevent.spawn(fetch_async, method='get', url='https://github.com/', req_kwargs={}),
])
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hsw Come on

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值