并发编程概念总结

进程

? 进程是计算机中最小的资源分配单位,进行中的一个程序就是一个进程。

进程需要操作系统来调度,每个程序运行起来的时候需要给分配一些内存,开启关闭切换时间开销大,进程之间数据隔离,进程也有数据不安全的问题 用Lock解决

进程的三状态图: 就绪 运行 阻塞
就绪-->操系统调度 -->运行-遇到io操作->阻塞-阻塞状态结束->就绪
                      -时间片到了->就绪

进程的调度算法:给所有的进程分配资源或者分配CPU使用权的一种方法。

  • 短作业优先、先来先服务、多级反馈算法
  • 多级反馈算法:
    • 多个任务队列,优先级从高到低
    • 新来的任务总是优先级最高
    • 每个新任务几乎会立即获得一个时间片时间
    • 执行完一个时间片之后就会降到下一级队列中
    • 总是优先级高的任务都执行完才执行优先级低的队列
    • 并且优先级越高时间片越短

进程开启和关闭
父进程 开启了子进程
父进程 负责给 子进程回收子进程结束之后的资源

from multiprocessing import Process
import os

def func():
   print(os.getpid(), os.getppid()) # pid process 子进程id    ppid 父进程id
if __name__ == '__main__':  # 只会在主进程中执行的所有的代码,写在__main__下边
   print('main:', os.getpid(), os.getppid())
   p = Process(target=func)  # target表示调用对象,即子进程要执行的任务
    p = Process(target=func,args=('安文',))
    # p = Process(target=func,kwargs={'name':'安文'})  两种传参方式
   p.start()  #异步非阻塞 启动进程,并调用该子进程中的p.run()

注意:在windows中Process()必须放到# if __name__ == '__main__':下
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。
这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。

开启进程的另一种方法:

  • 面向对象的方法,通过继承和重写run方法完成启动子进程
from multiprocessing import Process
# class 类名(Process):
#  def __init__(self,参数):
#     self.属性名=参数
#     super().__init__()
#  def run(self):
#     print("子进程要执行的代码")
# p=类名()
# p.start()
Process类的一些其他方法和属性:name  pid ident  daemon  terminate()  isalive()
p.name   给子进程起名字
is_alive()方法由线程调用,有返回值,如果线程还存活,返回true,如果线程消亡,返回false
terminate() #强制结束子进程 异步非阻塞
守护进程:
# 主进程会等待所有子进程结束,是为了回收子进程的资源
# 守护进程会等待主进程的代码执行结束之后再结束,而不是等待整个主进程结束
# 主进程的代码什么时候结束,守护进程就什么时候结束,和其他子进程的执行进度无关

主进程创建守护进程:

- 守护进程会在主进程代码执行结束后就终止
- 守护进程内无法再开启子进程,否则抛出异常

注意:进程之间是相互独立的,主进程代码运行结束,守护进程随即终止
在start一个进程之前设置daemon=True,守护进程会等待主进程的代码结束就立即结束
p = Process(target=son2)
p.daemon = True  # 一定要在p.start()前设置,表示设置p是一个守护进程
p.start()
为什么守护进程只守护主进程的代码?而不是等主进程结束之后才结束
#        为了给守护进程回收资源
#     守护进程会等其他子进程结束吗?不会
一般情况下,多个进程执行顺序可能是:
  • 主进程代码结束-->守护进程结束-->子进程结束-->主进程结束
  • 子进程结束-->主进程代码结束 -->守护进程结束-->主进程结束
进程之间通信(IPC):
  • 基于文件:同一台机器上的多个进程之间通信
    Queue队列:基于socket的文件级别的通信来完成数据传递的,

    ? 队列:安全 管道:不安全

  • 基于网络:同一台机器或者多台机器上的多进程之间的通信
    第三方工具:(消息中间件):memcache/redis/rabbitmq/kafka
进程之间可以通过Manager类实现数据共享

共享数据不安全,需要自己加锁解决数据安全问题

生产者模型 消费者模型:
  • 本质:让生产数据和消费数据的效率达到平衡并且最大化效率

消费者:通常取到数据之后还要进行某些操作 消费者如何结束:None
生产者:通常在放数据之前需要先通过某些代码来获取数据

# 把原本获取数据处理数据的完整过程进行了解耦
# 把生产数据和消费数据分开,根据生产和消费的效率不同,
# 来规划生产者和消费者的个数,让程序的执行效率达到平衡

#  如果你写了一个程序所有的代码、和功能都放在一起
# 不分函数不分类也不分文件,就叫这个程序是紧耦合的程序
# 紧耦合程序:代码只写一次,不需要重构
# 松耦合的程序:需要重构,不断迭代 复用代码

# 拆分的很清楚的程序 叫做 松耦合的程序,松耦合程序好
from multiprocessing import Queue,Pipe
# 队列:ipc进程之间通信,队列数据安全,不需要自己加锁。队列做通信,信息之间传递,
# 基于socket实现的,pickle实现,锁实现,
# pipe管道:也像队列一样,可以放数据可以取数据,没有锁数据不安全,
# 基于socket、pickle 实现的。没有锁 数据不安全
锁----multiprocessing.Lock:

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行修改,导致速度慢了,但保证了数据安全。

锁:保证数据安全,会降低程序的运行效率,数据安全

互斥锁,多进程中共享的数据需要加锁

# from multiprocessing import Lock  #互斥锁,多进程中共享的数据需要加锁
# lock=Lock()
# lock.acquire()
# '''被锁的内容在这里写'''
# lock.release()
# with lock:
#  ...

并行:

好,高效,多个cpu在自己的cpu上执行多个程序

并发:

一个cpu多个程序轮流执行,10个程序轮流使用一个cpu

多个程序同时执行,只要一个CPU,多个程序轮流在一个CPU上执行

宏观上:多个程序同时执行

微观上:多个程序轮流在一个CPU上执行,本质上是串行

同步异步阻塞非阻塞

同步:调用一个操作要等待结果
在做A件事的时候发起B事,必须等待B事件结束之后才能继续A事件
异步:更快,调用一个操作,不等待结果
在做A件事的时候发起B事,不需要等待B事件结束之后才能继续A事件

阻塞:如果CPU不工作 input accept recv sleep connect
非阻塞:如果CPU在工作

# 同步阻塞:调用一个函数需要等待这个函数的执行结果,并且在执行这个函数的过程中CPU不工作 num=input('>>>')
# 同步非阻塞:调用一个函数需要等待这个函数的执行结果,并且在执行这个函数的过程中CPU工作 ret=eval(1+2+3+4)
# 异步非阻塞:start() 调用一个函数不需要等待这个函数的执行结果,并且在执行这个函数的过程中CPU工作
# 异步阻塞:调用一个函数不需要等待这个函数的执行结果,并且在执行这个函数的过程中CPU不工作
#  10个异步的进程,获取这个进程的返回值,并且能做到哪一个进程结束,就先获取谁的返回值
# 同步阻塞
# 调用函数必须等待结果,cpu没工作,input sleep recv accept content get
# 同步非阻塞 *******
# 调用函数必须等待结果,cpu工作了,调用了一个高计算的函数 ,strip,eval,max,sorted同步非阻塞
# 异步阻塞
# 调用函数不需要立即获取结果,而是继续做其他的事情,在获取结果的时候不知道先获取谁的,但是总之要等
# 异步非阻塞 *******
# 调用函数不需要立即获取结果,也不需要等 start  terminate

线程

线程是计算机中能被CPU调度的最小单位,线程是进程中的一个单位,不能脱离进程存在,线程必须存在进程内。cpu执行的是解释之后的线程中的代码,同一个进程中的多个线程可以同时被CPU执行。线程之间的数据是共享的,操作系统调度的最小单位,可以利用多核,操作系统调度,数据不安全,开启关闭切换时间开销非常小

全局解释器 GIL (global interpreter lock):

  • cpython解释器下有个GIL锁全局解释器锁);全局解释器锁的出现主要是为了完成GC的回收机制,对不同线程的引用计数的变化记录的更加精准; 导致了同一个进程只能有一个线程真正被CPU执行,导致了同一个进程中的多个线程不能利用多核(不能并行)
  • 节省的是IO操作的时间,而不是CPU计算的时间,因为CPU的计算速度非常快,大部分情况下,我们没办法把一条进程中所有的io操作都规避掉

GC:垃圾回收机制,就是一个线程

pypy解释器 gc不能用多核

jpython解释器 gc能利用多核

开启线程:
# import time
# from threading import Thread
# def func(i):
#  print("start%s"%i)
#  time.sleep(1)
#  print("end%s"%i)
# for i in range(10):
#  Thread(target=func,args=(i,)).start()

# 面向对象方式起线程
# from threading import Thread
# class MyThread(Thread):
#   def __init__(self,a,b):
#       self.a=a
#       self.b=b
#       super().__init__()
#   def run(self):
#       print(self.ident)
# t=MyThread(1,2)
# t.start()   #开启线程 才在线程中执行run方法
# print(t.ident)
# 线程是不能从外部关闭的 没有terminate
# 所有的子线程只能是自己执行完代码之后就关闭
# current_thread()  当前线程的对象,current_thread().itent()线程的id
# enumerate() 列表  存储了所有活着的线程对象,包括主线程和子线程
# active_count() 数字  存储了所有活着的线程个数

主线程会等子线程结束之后才结束,子线程不结束,主线程就不结束

;因为主线程结束进程就会结束

守护线程 : 守护线程随着主线程的结束而结束;
守护线程会在主线程的代码结束之后继续守护其他子线程
# 守护进程  会随着主进程的代码结束而结束,
# 如果主进程代码结束之后还有其他子进程在运行,守护进程不守护
# 守护线程  会随着主线程的结束而结束
# 如果主线程代码结束之后还有其他子线程在运行,守护线程也守护

# 守护进程和守护线程的结束原理不同
# 守护进程需要主进程来回收、守护线程是随着进程的结束才结束;所有的线程都会随着进程的结束而被回收的
# 其他子线程-->主线程结束-->主进程结束-->整个进程中所有的资源都被回收-->守护线程也会被回收

线程之间数据不安全
# += -= *= /= while if 数据不安全 +和赋值是分开的两个操作
# append pop strip数据安全
# 列表中的方法或者字典中的方法去操作全局变量的时候 数据安全
线程锁:
单例模式加锁 天生线程安全
import time
class A:
   from threading import Lock
   __instance=None
   lock=Lock()
   def __new__(cls, *args, **kwargs):
      with cls.lock:
         if not cls.__instance:
            time.sleep(0.000001)
            cls.__instance=super().__new__(cls)
      return cls.__instance
def func():
   a=A()
   print(a)
from threading import Thread
for i in range(10):
   Thread(target=func).start()
互斥锁和递归锁的区别:
  • 递归锁 :(RLock)效率低,但是解决死锁现象有奇效 万能钥匙,临时解决一些死锁现象
  • 递归锁:在同一进程中可以被acquire多次,但一次acquire必须对应一次release
  • 互斥锁 :效率高,能够处理多个线程之间数据安全,但是多把互斥锁交替的使用容易产生死锁现象
  • 互斥锁:在同一个进程中不能被连续acquire多次,一次acquire对应一次release
死锁现象是怎么产生的?
多把(互斥锁/递归)锁并且在多个线程中交叉使用,(比如:两把锁,在第一把锁没有释放之前就获取第二把锁)
解决死锁现象:
  • ? 出现了死锁现象,最快速的解决方案把所有的互斥锁都改成一把递归锁,程序的效率会降低

队列:queue;

线程队列特点:数据安全,一定是加锁了,先进先出

什么是池?
  • 要在程序还没开始的时候,还没提交任务先创建几个线程或进程放在一个池子里
为什么要用池?
  • 如果先开好线程或者进程,那么有任务之后就可以直接使用这个池中的数据了,节省时间
  • 并且开好的线程或进程一直存在池中,可以被多个任务反复利用,这样极大的减少了开启和关闭、调度线程/进程的时间开销。
  • 池中的线程/进程个数控制了操作系统需要调度的任务个数,控制池中的单位有利于提高操作系统的效率,减轻操作系统的负担
# multiprocessing 模块 仿照threading写的pool
# concurrent.futures模块,线程池和进程池都能够用相似的方式开启和使用
#ThreadPoolExecutor:线程池,提供异步调用
#ProcessPoolExecutor: 进程池,提供异步调用
线程池:(一般根据io的比例定制)
import time
import random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def func(a,b):
    print(current_thread().ident,'start',a,b)   #接收参数
    time.sleep(random.randint(1,4))
    print(current_thread().ident,'end')
tp=ThreadPoolExecutor(4)
for i in range(20):
    tp.submit(func,i,i+1)   #按位置传参数
    tp.submit(func, a=i, b=i + 1)   #关键字传参数
进程池:
进程池(高计算场景,没有io(没有文件操作、没有数据库操作、没有网络操作
、没有input))
import os
import time
import random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

def func(a,b):
   print(os.getpid(),'start',a,b)   #接收参数
   time.sleep(random.randint(1,4))
   print(os.getpid(),'end')
if __name__ == '__main__':
   pp=ProcessPoolExecutor(4)
   for i in range(20):
      pp.submit(func,i,i+1)   #按位置传参数
      pp.submit(func, a=i, b=i + 1)   #关键字传参数
回调函数:效率最高
import time
import random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a,b):
   print(current_thread().ident,'start',a,b)   #接收参数
   time.sleep(random.randint(1,4))
   print(current_thread().ident,'end',a)
   return (a,a*b)
def print_func(ret):    #异步阻塞
   print(ret.result())
if __name__ == '__main__':
   tp=ThreadPoolExecutor(4)
   for i in range(20):     #提交任务是异步非阻塞
      ret=tp.submit(func,i,i+1)   #按位置传参数
      ret.add_done_callback(print_func)   #异步阻塞

# 回调函数 给ret对象绑定一个回调函数,等待ret对应的任务有了结果之后立即调用print_func这个函数
# 就可以对结果立即进行处理,而不用按照顺序接收处理结果

协程

协程:是操作系统不可见的
  • 协程本质就是一条线程,多个任务在一条线程上来回切换;
  • 利用协程这个概念实现的内容:规避io操作,达到将一条线程中的io操作降到最低的目的
# 切换并规避io操作的模块:
# gevent:利用了greenlet底层模块完成的切换+自动规避io的功能
# asyncio:利用了yield底层语法完成的切换+自动规避io的功能
# tornado异步的web框架
# yield from:为了更好的实现协程
#  send为了更好的实现协程
# asyncio 模块 基于Python原生的协程的概念正式被成立
# 特殊的在Python中提供协程功能的关键字:aysnc  await
用户级别的协程还有什么好处:
  • 减轻操作系统的负担
  • 一条线程如果开了多个协程,那么给操作系统的印象是线程很忙,
  • 这样能争取一些时间片时间来被cpu执行,程序的效率就提高了
协程:
import gevent
def func():
   print('start func')
   gevent.sleep(1)
   print('end func')
g=gevent.spawn(func)
g1=gevent.spawn(func)
gevent.joinall([g,g1])

asyncio:

import asyncio
async def func(name):   #async协程函数
   print("start",name)
   #await 关键字必须写在async函数里
   await asyncio.sleep(1)  #await后面可能会生成阻塞的方法
   print('end')
loop=asyncio.get_event_loop()  #事件循环
loop.run_until_complete(asyncio.wait([func('wudi'),func('anwen')]))

进程、线程和协程

# 进程:进程之间数据隔离,数据不安全,由操作系统(级别)切换,开销非常大,能利用多核
# 线程:线程之间数据共享,数据不安全,由操作系统(级别)切换,开销小,不能利用多核
# 协程:协程之间数据共享,数据安全  ,用户级别,开销更小,不能利用多核,协程的所有切换都基于用户,那么只有在用户级别
# 能够感知到的io操作才会用协程模块来切换来规避(socket,请求网页的)
asyncio
async def func(name):   #async协程函数
   print("start",name)
   #await 关键字必须写在async函数里
   await asyncio.sleep(1)  #await后面可能会生成阻塞的方法
   print('end')
loop=asyncio.get_event_loop()  #事件循环
loop.run_until_complete(asyncio.wait([func('wudi'),func('anwen')]))

进程、线程和协程

# 进程:进程之间数据隔离,数据不安全,由操作系统(级别)切换,开销非常大,能利用多核
# 线程:线程之间数据共享,数据不安全,由操作系统(级别)切换,开销小,不能利用多核
# 协程:协程之间数据共享,数据安全  ,用户级别,开销更小,不能利用多核,协程的所有切换都基于用户,那么只有在用户级别
# 能够感知到的io操作才会用协程模块来切换来规避(socket,请求网页的)

原文地址:https://www.cnblogs.com/an-wen/p/11284872.html

时间: 2024-10-07 05:09:35

并发编程概念总结的相关文章

JAVA并发编程>>概念准备

工于其善,必先利器 1.并发和并行的区别 并行:同一时间点执行多个任务(CPU多核或多个CPU同时执行多个任务) 并发:同一时间段内行多个任务(单核同时执行多个任务) 2.同步和异步的区别 同步:执行某个操作,按顺序执行下去,直到结束. 异步:执行某个操作后,立即离开,等到有返回结果时,回来继续执行. 额,感觉这个描述不怎么正式.但是我认为异步执行就是为了充分利用执行某项操作需要耗费大量时间,而异步就是为了利用这个时间,提高程序本身的执行效率. 3.进程和线程区别 进程:并发执行计算机程序的分配

【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

转自:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来

【java并发编程实战】-----线程基本概念

学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习Java并发编程,共同进步,互相指导. 在学习Java并发之前我们需要先理解一些基本的概念:共享.可变.线程安全性.线程同步.原子性.可见性.有序性. 共享和可变 要编写线程安全的代码,其核心在于对共享的和可变的状态进行访问. "共享"就意味着变量可以被多个线程同时访问.我们知道系统中的资

Java并发编程核心概念一览

并行相关概念 同步和异步 同步和异步通常来形容一次方法的调用.同步方法一旦开始,调用者必须等到方法结束才能执行后续动作:异步方法则是在调用该方法后不必等到该方法执行完就能执行后面的代码,该方法会在另一个线程异步执行,异步方法总是伴随着回调,通过回调来获得异步方法的执行结果. 并发和并行 很多人都将并发与并行混淆在一起,它们虽然都可以表示两个或者多个任务一起执行,但执行过程上是有区别的.并发是多个任务交替执行,多任务之间还是串行的:而并行是多个任务同时执行,和并发有本质区别. 对计算机而言,如果系

【并发编程】并发编程中你需要知道的基础概念

本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 多线程是Java编程中一块非常重要的内容,其中涉及到很多概念.这些概念我们平时经常挂在嘴上,但是真的要让你介绍下这些概念,你可能还真的讲不清楚.这篇博客就总结下多线程编程中经常用到的概念,理解这些概念能帮助我们更好地掌握多线程编程. 进程(Process)与线程(Thread) 进程和线程是最常提到的概念了.在linux中,线程与进程最大的区别就是是否共

Java并发编程(02):线程核心机制,基础概念扩展

本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效率.下面提供一个基础的演示案例. 2.应用案例 场景:假设有一个容器集合,需要拿出容器中的每个元素,进行加工处理,一般情况下直接遍历就好,如果数据偏大,可以根据线程数量对集合切割,每个线程处理一部分数据,这样处理时间就会减少很多. public class ExtendThread01 { publ

并发编程中的几个名词概念

现在,高并发,高流量已成为行业的热点,并且各种高并发的技术也是层出不穷,如论是官方文档还是市面上的各种书籍,我们在阅读的时候都会遇到一些专业名词,理解这些专业名词之后,才能更好的理解内容. 一.同步与异步 介绍: 同步和异步通常来形容一次方法调用. 解释一:同步方法调用一旦开始,调用者必须等到方法的调用返回后,才能继续后续的行为.异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作. 解释二:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信

理解并发编程中的重要概念:指令重排序和指令乱序执行

看过了很多介绍指令重排序的文章,可惜由于自己硬件和计算机理论知识缺乏,很难理解深层次的奥秘和实现原理.不过也有很多帖子,讲的浅显易懂,使用的例子很形象.大牛就是能用简单的解释和通俗的比喻,给我们讲明白很高深的东西.这里做个摘抄和总结,和大家分享下,希望大家能够对指令重排序有个形象的认识,不至于在并发编程中犯一些简单的错误.如果理解有错误,希望看到的大神指正. 从源码变成可以被机器(或虚拟机)识别的程序,至少要经过编译期和运行期.重排序分为两类:编译期重排序和运行期重排序(乱序执行),分别对应编译

Java并发编程(三)概念介绍

在构建稳健的并发程序时,必须正确使用线程和锁.但是这终归只是一些机制.要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问. 对象的状态是指存储在状态变量(例如实例或静态域)中的数据. 对象的状态可能包括其他依赖对象的域.比如某个HashMap的状态不仅是HashMap对象本身,还存储在许多Map.Entry对象中. "共享"意味着变量可以由多个线程同时访问,而"可变"则意味着变量的值在其生命周