线程、进程、协程

进程是资源分配单位,系统会分配内存,屏幕,窗口。
线程是进程中真正执行的东西。


 python中的thread模块是比较底层的模块,python的threading模块是对thread作了一些包装的,能够更加方便的被使用。python

一、多线程执行算法

 #coding=utf-8  import threading  import time  def saySorry(): print("亲爱的,我错了,我能吃饭了吗?") time.sleep(1)  if __name__ == "__main__": for i in range(5): t = threading.Thread(target=saySorry) t.start() #启动线程,即让线程开始执行
二、自定义线程类
#coding=utf-8 import threading import time class MyThread(threading.Thread): def __init__(self, name1, age): super(MyThread, self).__init__() self.name1 = name1 self.age = age def run(self): for i in range(3): time.sleep(1) msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字 print(msg) if __name__ == '__main__': t = MyThread() t.start()
 

      python的threading.Thread类有一个run方法,用于定义线程的功能函数,能够在本身的线程类中覆盖该方法。而建立本身的线程实例后,经过Thread类的start方法,能够启动该线程,交给python虚拟机进行调度,当该线程得到执行的机会时,就会调用run方法执行线程。缓存

 

      多线程之间共享全局变量,优势是方便在多个线程之间共享数据,缺点是线程是对全局变量随意遂改可能形成多线程之间对全局变量的混乱(即线程非安全)。安全

      若是多个线程同时对同一个全局变量操做,会出现资源竞争问题,从而数据结果会不正确:服务器



测试结果:import threading import time g_num = 0 def work1(num): global g_num for i in range(num): g_num += 1 print("----in work1, g_num is %d---"%g_num) def work2(num): global g_num for i in range(num): g_num += 1 print("----in work2, g_num is %d---"%g_num) print("---线程建立以前g_num is %d---"%g_num) t1 = threading.Thread(target=work1, args=(1000000,)) t1.start() t2 = threading.Thread(target=work2, args=(1000000,)) t2.start() while len(threading.enumerate()) != 1: time.sleep(1) print("2个线程对同一个全局变量操做以后的最终结果是:%s" % g_num)
---线程建立以前g_num is 0--- ----in work1, g_num is 1088005--- ----in work2, g_num is 1286202--- 2个线程对同一个全局变量操做以后的最终结果是:1286202

同步:网络

同步就是协同步调,按预约的前后次序进行运行多线程

互斥锁:并发

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其余线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其余的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操做,从而保证了多线程状况下数据的正确性。app

写法:异步

# 建立锁 mutex = threading.Lock() # 锁定 mutex.acquire() # 释放 mutex.release()

 

锁的好处:

确保了某段关键代码只能由一个线程从头至尾完整地执行
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地降低了
因为能够存在多个锁,不一样的线程持有不一样的锁,并试图获取对方持有的锁时,可能会形成死锁

死锁解决办法(

看门狗思想:过一一段时间就执行一次特殊的某行代码,若是长时间不执行,系统就自动重启

2.银行家算法

互斥锁:线程可以同步保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其余线程不能更改,直到该线程释放资源。

当建立一个线程以后,函数里面全部的内存空间是这个线程独有的,在建立一个时,会从新建立一个内存空间。各人是各人的。函数里面的代码各人是各人的,不会共享。

非全局变量不须要加锁。

死锁:在线程间共享多个资源的时候,若是两个线程分别占有一部分资源而且同时等待对方的资源时,就会形成死锁。

同步:同步就是协调步调,按照预约的前后次序进行运行  三把锁一环扣一环
异步:不一样步

生产者与消费者模式:
1。队列 :进程中的队列和线程中的队列不是一个概念。队列就是用来给生产者和消费者解耦的。
2。栈
fifo: frist in frist out =>Queue
filo: frist in last out =>

一个函数想获得另外一个函数的值,要么return 返回值,要么经过全局变量。

一、使用全局字典的方法
二、ThreadLocal:不用传参数,用一个全局变量,能过完成线程里边的全部的数据的传递,不会由于多个线程对参数的修改对程序产生影响。

孤儿进程:父进程先结束,子进程还没结束
僵尸进程:若是一个子进程死了,父进程没有收尸,在收尸前的整个期间,子进程就称为僵尸进程。

线程之间共享全局变量。

原子操做(原子性):要么不作,要作就作完。

线程安全问题:可能在一句代码还没执行完,操做系统就中止了代码的运行。

轮询:是一种CPU决策如何提供周边设备服务的方式,又称程控输出入

多任务UDP聊天器:

import socket import threading # 定义发送数据的函数 def send_data(udp_socket): # 定义要发送的内容 send_content = input("请输入要发送的内容:") # 请输入IP地址 ipddr = input("请输入IP地址,格式为:xxx.xxx.xxx.xxx :") # 请输入端口号 port = int(input("请输入端口号:")) # 把要发送的数据转换为 二进制 send_data = send_content.encode("utf-8") # 发送数据 udp_socket.sendto(send_data, (ipddr, port)) # 测试 # 定义接收数据的函数 def recvData(udp_socket): while True: # 接收数据 recv_data = udp_socket.recvfrom(1024) # 若是数据存在,则解析数据 if recv_data: # 拆包,获得内容 msg, list_port = recv_data # 拆包,获得msg msg = msg.decode("gbk") # 打印内容 print(msg, list_port) else: # 数据不存在中止循环 break # 定义主入口函数 def main(): # 定义套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定服务器客户端端口 # udp_socket.bind(("", 7878)) # 定义子线程而且开启 t1 = threading.Thread(target=recvData, args=(udp_socket, )) # 设置守护线程 t1.setDaemon(True) t1.start() while True: print("-------------------") print("-- 一、发送数据 --") print("-- 二、退出系统 --") print("-------------------") num = int(input("请选择功能【1/2/3】:")) if num < 1 or num > 3: print("输入不合法!,请从新输入") else: if num == 1: # 发送数据 send_data(udp_socket) if num == 2: print("正在退出系统...") print("系统已退出") break if __name__ == '__main__': main()

===========================================================================

进程:操做系统中的算法包括:时间片轮转、优先级调度、


并发:看上去一块儿执行。当前的任务数量大于核数。
并行:真正的一块儿执行。当前任务数小于核数。

调度算法:什么样的状况下按照什么样的规则让谁去执行。
编写完毕的代码,在没有运行的时候称之为程序,在运行的时候称之为进程。

一、fork()创造子线程
import os
fork():能够在python程序中建立子进程。
ret = os.fork()
在fork()中,主进程想要结束,不会由于子进程没有结束而等待。只要子进程产生,子进程的执行顺序和执行过程和主进程同样,就是众所周知的代码执行的过程。


2.pid值:
getpid():获取当前进程的pid值。
pid值:在操做系统当中,当进程运行起来时,操做系统都会给这个进程分配一个独一无二的值,即pid值。processID
父进程中fork的返回值,就是刚刚建立出来的子进程的id。
getppid():获取父进程的pid值。
pid值小于等于65535


3.Process()创造子线程:

import multiprocessing import time # 定义函数 def work1(): for i in range(10): print("work1----", i) time.sleep(0.5) if __name__ == '__main__': # 建立进程 # 1. 导入 multiprocessing 模块 # 2. multiprocessing.Process() 建立子进程 # 3. start() 方法启动进程 p1 = multiprocessing.Process(group=None, target=work1) p1.start() for i in range(10): print("这是主进程", i) time.sleep(0.5)

 

 

    p.join()#加了join以后,主进程会等子进程执行完代码以后,再开始执行join下面的代码

    join([timeout])#堵塞:主进程等待子进程结束以后才结束。timeout表示操做时间。
    terminate():无论任务是否完成,当即终止。

    因为process的跨平台更好,之后不用fork,而是用process

 

 

Process语法结构以下:

Process([group [, target [, name [, args [, kwargs]]]]])
target:若是传递了函数的引用,能够任务这个子进程就执行这里的代码
args:给target指定的函数传递的参数,以元组的方式传递
kwargs:给target指定的函数传递命名参数
name:给进程设定一个名字,能够不设定

group:指定进程组,大多数状况下用不到

Process建立的实例对象的经常使用方法:
start():启动子进程实例(建立子进程)
is_alive():判断进程子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒

terminate():无论任务是否完成,当即终止子进程

Process建立的实例对象的经常使用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

pid:当前进程的pid(进程号)

 

进程间不一样享全局变量

进程线程对比:

进程,可以完成多任务,好比 在一台电脑上可以同时运行多个QQ

线程,可以完成多任务,好比 一个QQ中的多个聊天窗口

进程是系统进行资源分配和调度的一个独立单位.

 

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),可是它可与同属一个进程的其余的线程共享进程所拥有的所有资源.
区别:
一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程当中拥有独立的内存单元,而多个线程共享内存,从而极大地提升了程序的运行效率 
线线程不可以独立执行,必须依存在进程中
能够将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人 
优缺点:

线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

 

4.进程池Pool建立子线程

import multiprocessing import time def copy_work(): print("拷贝中....",multiprocessing.current_process().pid) time.sleep(0.3) if __name__ == '__main__': # 建立进程池 # Pool(3) 表示建立容量为3个进程的进程池 pool = multiprocessing.Pool(3) for i in range(10): # 利用进程池同步拷贝文件,进程池中的进程会必须等上一个进程退出才能执行下一个进程 # pool.apply(copy_work) pool.apply_async(copy_work) pool.close() # 注意:若是使用异步方式执行copy_work任务,主线程再也不等待子线程执行完毕再退出! pool.join()

 

 

进程池Pool:主进程通常用来等待,真正的任务都在子进程中执行。

 

multiprocessing.Pool经常使用函数解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
close():关闭Pool,使其再也不接受新的任务;
terminate():无论任务是否完成,当即终止;

join():主进程阻塞,等待子进程的退出, 必须在close或terminate以后使用;

 

进程池中的Queue
若是要使用Pool建立进程,就须要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),不然会获得一条以下的错误信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.

下面的实例演示了进程池中的进程如何通讯:

import multiprocessing import time def write_queue(queue): # 循环写入数据 for i in range(10): if queue.full(): print("队列已满!") break # 向队列中放入消息 queue.put(i) time.sleep(0.5) def read_queue(queue): # 循环读取队列消息 while True: # 队列为空,中止读取 if queue.empty(): print("---队列已空---") break # 读取消息并输出 result = queue.get() print(result) if __name__ == '__main__': # 建立消息队列 queue = multiprocessing.Queue(3) # 建立子进程 p1 = multiprocessing.Process(target=write_queue, args=(queue,)) p1.start() # 等待p1写数据进程执行结束后,再往下执行 p1.join() p1 = multiprocessing.Process(target=read_queue, args=(queue,)) p1.start()

q.put():存数据  q.get():取数据  q.full():判断数据是不是满的  q.empty()判断数据是否为空  q.get_nowait():当即存数据不等待   q.put_nowait():当即取数据不等待


进程间通讯的方式:命名管道  无名管道 共享内存 队列  网络功能

 

fork ()是最底层的方法。
pool = Pool(3)
pool.apply_async(xx)
pool 中,主进程通常不干活,主要是建立的子进程干活,join()方法用来等待。


apply()=>堵塞式


进程共享数据,写实拷贝。


主进程的pid 是运行程序的那个软件的pid值

案例:文件夹copy器

import multiprocessing import os # file_name 文件名 # source_dir 源文件目录 # dest_dir 目标文件目录 def copy_work(file_name, source_dir, dest_dir): # 拼接路径 source_path = source_dir+"/"+file_name dest_path = dest_dir+"/"+file_name print(source_path, "----->", dest_path) # 打开源文件、建立目标文件 with open(source_path,"rb") as source_file: with open(dest_path,"wb") as dest_file: while True: # 循环读取数据 file_data = source_file.read(1024) if file_data: # 循环写入到目标文件 dest_file.write(file_data) else: break if __name__ == '__main__': # 一、定义源文件目录和目标文件夹的目录 source_dir = "test" dest_dir = "/home/teahcer/桌面/test" try: # 二、建立目标文件夹目录 os.mkdir(dest_dir) except: print("目标文件夹已经存在,未建立~") # 三、列表获得全部的源文件中的文件 file_list = os.listdir(source_dir) print(file_list) # 四、建立进程池 pool = multiprocessing.Pool(3) # 五、for 循环,依次拷贝每一个文件 for file_name in file_list: # copy_work(file_name, source_dir, dest_dir) pool.apply_async(copy_work, args=(file_name, source_dir, dest_dir)) # 六、关闭进程池 pool.close() # 七、设置主进程等待子进程执行结束再退出 pool.join()

============================================================================

协程:

迭代器(iterator):

迭代是访问集合元素的一种方式。

迭代器是一个能够记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到全部的元素被访问完结束。

迭代器只能往前不会后退。

能够对list、tuple、str等类型的数据使用for...in...的循环语法从其中依次拿到数据进行使用,这样的过程称为遍历,也叫迭代。

可迭代对象(Iterable):能够经过for...in...这类语句迭代读取一条数据供咱们使用的对象。列表元组字典都是  可迭代对象。

可迭代对象经过__iter__方法向咱们提供一个迭代器,咱们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,而后经过这个迭代器来依次获取对象中的每个数据.

一个具有了__iter__方法的对象,就是一个可迭代对象。

一个实现了__iter__方法和__next__方法的对象,就是迭代器。

iter()函数与next()函数
list、tuple等都是可迭代对象,咱们能够经过iter()函数获取这些可迭代对象的迭代器。而后咱们能够对获取到的迭代器不断使用next()函数来获取下一条数据。iter()函数实际上就是调用了可迭代对象的__iter__方法。

 
  1.  
    >>> li = [11, 22, 33, 44, 55]
  2.  
    >>> li_iter = iter(li)
  3.  
    >>> next(li_iter)
  4.  
    11
  5.  
    >>> next(li_iter)
  6.  
    22
  7.  
    >>> next(li_iter)
  8.  
    33
  9.  
    >>> next(li_iter)
  10.  
    44
  11.  
    >>> next(li_iter)
  12.  
    55
  13.  
    >>> next(li_iter)
  14.  
    Traceback (most recent call last):
  15.  
    File "<stdin>", line 1, in <module>
  16.  
    StopIteration
  17.  
    >>>

可使用 isinstance() 判断一个对象是不是 Iterator 对象.

一个实现了__iter__方法和__next__方法的对象,就是迭代器。

自定义迭代器

from collections import Iterable from collections import Iterator class StudentList(object): def __init__(self): # 建立列表 self.items = list() def addItem(self,item): # 追加元素到列表中 self.items.append(item) def __iter__(self): # 建立迭代器对象 studentIterator = StudentIterator(self.items) # 返回迭代器对象 return studentIterator # 定义迭代器 class StudentIterator(object): # 定义构造方法 # 1)完成 索引下标定义和初始化 # 2)接收要遍历的列表值 def __init__(self, items): self.items = items self.current_index = 0 def __iter__(self): return self def __next__(self): # 判断位置是否合法 if self.current_index < len(self.items): # 根据current_index 返回列表值 item = self.items[self.current_index] # 让 下标+1 self.current_index += 1 # 返回元素内容 return item else: # 中止迭代 # 主动抛出异常,迭代器没有更多的值(到了迭代器末尾) raise StopIteration # 实例化对象 stulist = StudentList() stulist.addItem("张三") stulist.addItem("李四") stulist.addItem("C罗") # 检查是不是可迭代对象 result = isinstance(stulist, Iterable) print(result) for value in stulist: print(value)

for...in...循环的本质
for item in Iterable 循环的本质就是先经过iter()函数获取可迭代对象Iterable的迭代器,而后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

 

8. 迭代器的应用场景

咱们发现迭代器最核心的功能就是能够经过next()函数的调用来返回下一个数据值。若是每次返回的数据值不是在一个已有的数据集合中读取的,而是经过程序按照必定的规律计算生成的,那么也就意味着能够不用再依赖一个已有的数据集合,也就是说不用再将全部要迭代的数据都一次性缓存下来供后续依次读取,这样能够节省大量的存储(内存)空间。

斐波那契数:

class Fibonacci(): def __init__(self, num): # 经过构造方法,保存num到类的成员属性中 self.num = num # 定义变量保存斐波那契数列前两个值 self.a = 0 self.b = 1 # 记录当前的变量值 self.current_index = 0 def __iter__(self): # 返回迭代器,因自身就是迭代器,故能够返回本身 return self def __next__(self): # 判断是否生成完毕 if self.current_index < self.num: # 返回 result = self.a # 交换两个变量值 self.a, self.b = self.b, self.a+self.b self.current_index += 1 return result else: # 中止迭代 raise StopIteration if __name__ == '__main__': # 建立迭代器 fib_iterator = Fibonacci(5) # 使用迭代器,输出斐波那契数列值 for value in fib_iterator: print(value, end=" ")

并非只有for循环能接收可迭代对象,除了for循环能接收可迭代对象,list、tuple等也能接收。

  1.  
    li = list(FibIterator( 15))
  2.  
    print(li)
  3.  
    tp = tuple(FibIterator( 6))
  4.  
    print(tp)

生成器:

 

利用迭代器,咱们能够在每次迭代获取数据(经过next()方法)时按照特定的规律进行生成。可是咱们在实现一个迭代器时,关于当前迭代到的状态须要咱们本身记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,咱们能够采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。
建立生成器方法1
要建立一个生成器,有不少种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改为 ( )。

In [15]: L = [ x*2 for x in range(5)] In [16]: L Out[16]: [0, 2, 4, 6, 8] In [17]: G = ( x*2 for x in range(5)) In [18]: G Out[18]: <generator object <genexpr> at 0x7f626c132db0>

 

 建立生成器方法2
generator很是强大。若是推算的算法比较复杂,用相似列表生成式的 for 循环没法实现的时候,还能够用函数来实现。

 

使用了yield关键字的函数再也不是函数,而是生成器。(使用了yield的函数就是生成器)
yield关键字有两点做用:
保存当前运行状态(断点),而后暂停执行,即将生成器(函数)挂起
将yield关键字后面表达式的值做为返回值返回,此时能够理解为起到了return的做用
可使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)

Python3中的生成器可使用return返回最终运行的返回值,而Python2中的生成器不容许使用return返回一个返回值(便可以使用return从生成器中退出,但return后不能有任何表达式)。

 

使用send唤醒

咱们除了可使用next()函数来唤醒生成器继续执行外,还可使用send()函数来唤醒执行。使用send()函数的一个好处是能够在唤醒的同时向断点处传入一个附加数据。

例子:执行到yield时,gen函数做用暂时保存,返回i的值; temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)

协程:

又称微线程。通俗的理解:在一个线程中的某个函数,能够在任何地方保存当前函数的一些临时变量等信息,而后切换到另一个函数中执行,注意不是经过调用函数的方式作到的,而且切换的次数以及何时再切换到原来的函数都由开发者本身肯定

 

协程和线程差别:

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操做系统为了程序运行的高效性每一个线程都有本身缓存Cache等等数据,操做系统还会帮你作这些数据的恢复操做。 因此线程的切换很是耗性能。可是协程的切换只是单纯的操做CPU的上下文(能够理解为方法之间的切换),因此一秒钟切换个上百万次系统都抗的住。简单说,就是比线程占用资源更少。

用yield()和next()实现简单协程:

import time def work1(): while True: print("----work1---") yield time.sleep(0.5) def work2(): while True: print("----work2---") yield time.sleep(0.5) def main(): w1 = work1() w2 = work2() while True: next(w1) next(w2) if __name__ == "__main__": main()

用greenlet实现协程:

greenlet 至关于集成了yield()和next(),使用的时候自动调用这两个方法。

先装包:sudo pip3 install preenlet

#coding=utf-8 from greenlet import greenlet import time def test1(): while True: print "---A--" gr2.switch() time.sleep(0.5) def test2(): while True: print "---B--" gr1.switch() time.sleep(0.5) gr1 = greenlet(test1) gr2 = greenlet(test2) #切换到gr1中运行 gr1.switch()

用gevent实现协程:

gevent比greenlet更厉害,直不用调用switch方法,能够自动切换任务。

gevent的原理:当一个greenlet遇到IO(指的是input output 输入输出,好比网络、文件操做等)操做时,好比访问网络,就自动切换到其余的greenlet,等到IO操做完成,再在适当的时候切换回来继续执行。

因为IO操做很是耗时,常常使程序处于等待状态,有了gevent为咱们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

安装:pip install gevent

import time import gevent def work1(): for i in range(5): print("work1 -----1") time.sleep(0.5) def work2(): for i in range(5): print("work2 -----2") time.sleep(0.5) # 建立携程并指派任务 g1 = gevent.spawn(work1) g2 = gevent.spawn(work2) # 等待协程执行完成再关闭主线程 g1.join() g2.join()

注意:上面代码中的time方法用的是gevent包中的。

用gevent时,只要加入堵塞的方法都须要gevent里面的,全部的延时堵塞方法都要用gevent里面的。

这样使用时就不是很方便,可能会不知道哪些方法须要用gevent里面的 。所以须要导入monkey。并在代码的最前面写一句:

monkey .patch_all()#至关于monkey把代码中的全部延时操做都改为用gevent里面的。

 

协程并发下载器:

# 导入urllib模块 import urllib.request import gevent def download_img(img_url, filename): try: # 打开url response = urllib.request.urlopen(img_url) # 建立文件 with open(filename, "wb") as img_file: # 经过循环不断读取数据 while True: # 将读取到的数据保存到变量中 img_data = response.read(1024) # 若是读取成功,则写数据到文件中 if img_data: # 写数据 img_file.write(img_data) else: break except Exception as e: print("下载图片出现错误~!",e) else: print("图片 %s 下载完成!" % filename) def main(): # 定义变量保存要下载的图片地址 img_url1 = "http://img.mp.itc.cn/upload/20170716/8e1b835f198242caa85034f6391bc27f.jpg" img_url2 = "http://pic1.wed114.cn/allimg/180227/1023303521-1.gif" img_url3 = "http://image.uczzd.cn/11867042470350090334.gif?id=0&from=export" # 开启协程 调用下载方法 gevent.joinall([ gevent.spawn(download_img, img_url1, "1.gif"), gevent.spawn(download_img, img_url2, "2.gif"), gevent.spawn(download_img, img_url3, "3.gif") ]) # 主入口 if __name__ == '__main__': main()

 

进程线程协程之间的区别:

进程是资源分配的单位线程是操做系统调度的单位进程切换须要的资源很最大,效率很低线程切换须要的资源通常,效率通常(固然了在不考虑GIL的状况下)协程切换任务资源很小,效率高多进程、多线程根据cpu核数不同多是并行的,可是协程是在一个线程中 因此是并发

相关文章
相关标签/搜索