python的进程线程协程--李继昂的技术博客

进程

进程定义

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

进程定义拓展回答内容

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

多道编程概念

  1. 多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行
  2. 单道编程: 计算机内存中只允许一个的程序运行

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

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

进程池

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()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

进程和程序的区别

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

线程

线程定义

  1. 线程是操作系统调度的最小单位
  2. 它被包含在进程之中,是进程中的实际运作单位
  3. 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合

线程定义拓展回答内容

  1. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
  2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
  3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
  4. 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
  5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
  6. 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程
  7. 两个进程想通信,必须要通过一个中间代理
  8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存

进程和线程的区别

  1. 进程包含线程
  2. 线程共享内存空间
  3. 进程内存是独立的(不可互相访问)
  4. 进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
  5. 在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
  6. 创建新线程很简单,创建新进程需要对其父进程进行克隆。
  7. 一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
  8. 父进程可以修改不影响子进程,但不能修改。
  9. 线程可以帮助应用程序同时做几件事

for循环同时启动多个线程

import threading
import 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.start()

t.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全局解释器锁:保证同一时间仅有一个线程对资源有操作权限

作用:在一个进程内,同一时刻只能有一个线程执行
说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多

  1. 为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL
  2. GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程
  3. 为了解决这个问题,CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据
  4. python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口
  5. 但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷

线程锁

  1. 当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作
  2. 这样就可以防止还未计算完成,释放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(信号量)

  1. 互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据
  2. 比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
  3. 作用就是同一时刻允许运行的线程数量

线程池实现并发

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)                      # 主线程自己关闭,让子线程自己拿任务执行

协程

什么是协程(进入上一次调用的状态)

  1. 协程,又称微线程,纤程,协程是一种用户态的轻量级线程。
  2. 线程的切换会保存到CPU的栈里,协程拥有自己的寄存器上下文和栈,
  3. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈
  4. 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态
  5. 协程最主要的作用是在单线程的条件下实现并发的效果,但实际上还是串行的(像yield一样)

协程缺点(无法利用多核资源)

协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上线程阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

协程为何能处理大并发1:Greenlet遇到I/O手动切换

  1. 协程之所以快是因为遇到I/O操作就切换(最后只有CPU运算)
  2. 这里先演示用greenlet实现手动的对各个协程之间切换
  3. 其实Gevent模块仅仅是对greenlet的再封装,将I/O间的手动切换变成自动切换

协程为何能处理大并发2:Gevent遇到I/O自动切换

  1. Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程
  2. 在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程
  3. Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
  4. Gevent原理是只要遇到I/O操作就会自动切换到下一个协程

5. 使用协程处理并发

注: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={}),
])

猜你喜欢

转载自blog.csdn.net/m0_47202787/article/details/106625226