并发编程小结

目录

  • 多道技术
  • 并发与并行
  • 进程
    • 程序与进程
    • 进程调度
    • 进程的状态
  • 同步异步阻塞非阻塞
  • 创建进程的两种方式
  • 回收进程资源的两种方式
  • 僵尸进程、孤儿进程、守护进程
  • 进程互斥锁
  • 进程间通信
    • 队列
    • 堆栈
  • 生产者与消费者模型
  • 线程
    • 进程与线程的优缺点
    • 线程间数据是共享的
  • GIL全局解释器锁
  • 死锁与递归锁
    • 死锁
    • 递归锁
  • 信号量
  • Event事件
  • 线程队列
  • 进程池与线程池
  • 协程
    • gevent
  • IO模型

多道技术

单道:一台哦到

多道:

  • 时间上复用, 遇到IO操作就会切换,程序占用CPU时间过长就会切换
  • 空间上复用, 支持多个程序

并发与并行

并发:看起来像是同时运行

并行:真正意义上的同时运行

并行与并发的区别:

并行是从微观上,也就是一个精确的时间片刻,有不同的程序在执行

并发是从宏观上,在一个时间段上可以看出是同时执行的

进程

进程是资源单位,每创建一个进程都会生成一个名称空间,占用内存资源

程序与进程

程序就是一堆代码

进程:狭义上来讲一堆代码运行的过程;广义上讲,进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统动态执行的基本单元

进程调度

  1. 先来先服务调度算法
  2. 短作业优先调度算法
  3. 时间片轮转法
  4. 多级反馈队列

进程的状态

  • 就绪
  • 运行
  • 阻塞

同步异步阻塞非阻塞

同步:执行一个操作之后,等待通知,然后才执行接下来的操作

异步:执行一个操作之后,可以去执行其他的操作,然后等待通知 回来执行未完成的操作。

阻塞:进程给cpu传达一个任务之后,一直等待cpu处理完成,然后才执行接下来的操作

非阻塞:进程给cpu传达任务后,继续处理后续的操作,隔段时间再来询问之前的操作是否完成。

阻塞与非阻塞是指进程访问的数据,如果尚未就绪,进程是否需要等待,指的是一种状态。

同步与异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后,在读写的时候必须阻塞;异步则指主动请求数据后,便可以继续处理其它任务,随后等待I/O操作完毕的通知,这可以使进程在数据读写时也不阻塞。

创建进程的两种方式

使用multiprocessing模块

# 方法一

from multiprocessing import Process
import time

def task():
    print('子进程开始。。。')
    time.sleep(2)
    print('子进程结束。。。')

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()   # 等待所有子进程结束后,主进程才结束
    print('主进程') 

# 方法二:
class MyProcess(Process):
    def run(self) -> None:
        print('子进程开始。。。')
        time.sleep(2)
        print('子进程结束。。。')

if __name__ == '__main__':
    p = MyProcess()
    p.start()
    p.join()
    print('主进程')

回收进程资源的两种方式

  • 调用join,等待子进程结束后主进程结束
  • 主进程正常结束

僵尸进程、孤儿进程、守护进程

僵尸进程:子进程结束,PID号还在,主进程没有回收子进程资源

孤儿进程:子进程没有结束,但是主进程意外死亡,操作系统优化机制会将子进程结束之后回收资源。

守护进程:

  • 守护进程会在主进程代码结束之后就终止。
  • 守护进程内无法再开启子进程

进程互斥锁

from multiprocessing import Lock

进程间数据不共享,但共享同一套文件系统,多个进程访问同一个文件会造成混乱、数据不安全,必须加锁。

总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,这样虽然牺牲了效率,但保证了数据的安全

from multiprocessing import Lock
mutex = Lock()

mutex.acquire()# 加锁
# 修改数据的操作
mutex.release()# 释放锁

进程间通信

进程间数据是隔离的,需要通过队列的方式进行通信

队列

FIFO队列:先进先出

from multiprocessing import Queue
q = Queue(5)

q.put()  #添加数据,若队列满了,则等待
q.put_nowait() # 添加数据,若队列添加数据满了,就会直接报错

q.get() # 若队列中没有数据,就会卡住等待
q.put_nowait() # 若队列中没有数据,会直接报错

堆栈

LIFO:后进先出

生产者与消费者模型

生产者:生产数据

消费者:使用数据

通过队列实现,生产者将数据扔进队列中,消费者从队列中获取数据,从而平衡了生产者与消费者的处理能力

线程

进程是资源单位,而线程是执行单位。

创建进程会自带一个线程,一个进程下可以创建多个线程

使用线程可以节省资源的开销

进程与线程的优缺点

进程的优点:

  • 多核下可以并行
  • 计算密集型下提高效率

进程的缺点:

  • 开销资源远高于线程

线程的优点:

  • 占用资源远比进程小
  • IO密集型下能提高效率

线程的缺点:

  • 无法利用多核优势

线程间数据是共享的

GIL全局解释器锁

  • 只有cpython才自带有一个GIL全局解释器锁
  • GIL本质上是一个互斥锁
  • GIL是为了阻止同一个进程内多个线程同时运行。单个进程下的多线程只能实现并发
  • GIL锁主要是因为cpython的内存管理不是“线程安全”的,多个线程执行,遇到IO操作,会立马释放GIL锁,交给下一个先进来的线程

总结:GIL锁主要是为了保证线程安全的,保证数据安全

死锁与递归锁

死锁

指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种相互等待的现象,若无外力作用,它们都无法推进下去,这就是死锁现象。

from threading import Lock
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

递归锁

解决死锁现象

mutex = Lock()
mutex1, mutex2 = Rlock()  # 可以引用多次
只要这把锁计数为0,就释放该锁,让下一个人使用

信号量

from threading import Semaphore

信号量也是一把锁,可以让多个任务一起使用

Event事件

可以控制线程的执行,让一些线程控制另一些线程的执行

e = Event()

- 线程1
e.set()  # 给线程2发送信号,让他执行

- 线程2
e.wait()  # 等待线程1的信号

线程队列

线程间数据不安全的情况下使用线程队列,保证线程间数据安全

import queue
FIFO: 先进先出队列   queue.Queue()

LIFO: 后进先出队列   queue.LifoQueue()

优先级队列:根据数据大小判断出队列优先级;进队列数据是无序的
queue.PriorityQueue()

进程池与线程池

为了控制进程/线程创建的数量,保证了硬件能正常运行

from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import ThreadPoolExecutor

poo1 = ProcessPoolExecutor()  # 括号里不加数字表示默认
poo2 = ThreadPoolExecutor(100)

# 将函数地址的执行结果,给回调函数
pool2.submit(任务函数地址,参数).add_done_callback(回调函数地址)

回调函数(必须接收一个参数res)
res2 = res.result()  # 获取值

协程

单线程下实现并发,不是任何单位

单线程下实现并发,好处是节省资源

  • 在IO密集型的情况下,协程有优势
  • 在计算密集型的情况下,进程you优势

手动创建协程:

  • 手动实现切换 + 保存状态

    • yield 可以保存状态
    • 通过调用next,可以不停的切换
  • yield不能监听IO操作

gevent

第三方模块,用gevent可以实现监听IO操作

from gevent import monkey
monkey.path_all()  # 监听所有IO
from gevent import spawn, joinall  # spawn实现切换 + 保存状态

s1 = spawn(任务1)
s2 = spawn(任务2)
joinall([s1, s2])

IO模型

  • 阻塞IO
  • 非阻塞IO
  • 多路复用IO
  • 异步IO

原文地址:https://www.cnblogs.com/setcreed/p/11748693.html

时间: 2024-11-09 05:12:20

并发编程小结的相关文章

java并发编程小结

旭日Follow_24 的CSDN 博客 ,全文地址请点击: https://blog.csdn.net/xuri24/article/details/82078467 线程简介: 线程是操作系统调度的最先单元,进程:线程=1:N 关系,也就是说一个进程可以创建多个线程,至少包含一个线程.多线程可以最大限度的使用CPU和维护各线程之间的并发进行关系等. 一.concurrent并发包 locks部分:显式锁(互斥锁和速写锁)相关: atomic部分:原子变量类相关,是构建非阻塞算法的基础: ex

JAVA并发编程J.U.C学习总结

前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www.cnblogs.com/chenpi/p/5614290.html 本文目录如下,基本上涵盖了J.U.C的主要内容: JSR 166及J.U.C Executor框架(线程池. Callable .Future) AbstractQueuedSynchronizer(AQS框架) Locks & C

《C++ 并发编程》- 第1章 你好,C++的并发世界

<C++ 并发编程>- 第1章 你好,C++的并发世界 转载自并发编程网 – ifeve.com 本文是<C++ 并发编程>的第一章,感谢人民邮电出版社授权并发编程网发表此文,版权所有,请勿转载.该书将于近期上市. 本章主要内容 何谓并发和多线程 为什么要在应用程序中使用并发和多线程 C++并发支持的发展历程 一个简单的C++多线程程序是什么样的 这是C++用户的振奋时刻.距1998年初始的C++标准发布13年后,C++标准委员会给予程序语言和它的支持库一次重大的变革.新的C++标

并发编程 16—— Lock

Java并发编程实践 目录 并发编程 01—— ConcurrentHashMap 并发编程 02—— 阻塞队列和生产者-消费者模式 并发编程 03—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程 04—— Callable和Future 并发编程 05—— CompletionService : Executor 和 BlockingQueue 并发编程 06—— 任务取消 并发编程 07—— 任务取消 之 中断 并发编程 08—— 任务取消 之 停止基于线

并发编程—— CompletionService : Executor 和 BlockingQueue

Java并发编程实践 目录 并发编程—— ConcurrentHashMap 并发编程—— 阻塞队列和生产者-消费者模式 并发编程—— 闭锁CountDownLatch 与 栅栏CyclicBarrier 并发编程—— Callable和Future 并发编程—— CompletionService : Executor 和 BlockingQueue 概述 第1部分 问题引入 第2部分 第1部分 问题引入 <Java并发编程实践>一书6.3.5节CompletionService:Execu

iOS并发编程对比总结,NSThread,NSOperation,GCD - iOS

1. 多线程概念 进程 正在进行中的程序被称为进程,负责程序运行的内存分配 每一个进程都有自己独立的虚拟内存空间 线程 线程是进程中一个独立的执行路径(控制单元) 一个进程中至少包含一条线程,即主线程 可以将耗时的执行路径(如:网络请求)放在其他线程中执行 创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行 1.1 多任务系统调度示意图 说明:每个应用程序由操作系统分配的短暂的时间片(Timeslice)轮流使用CPU,由于CPU对每个时间片的处理速度非常快

并发编程之三

引言 很久没有跟大家再聊聊并发了,今天LZ闲来无事,跟大家再聊聊并发.由于时间过去的有点久,因此LZ就不按照常理出牌了,只是把自己的理解记录在此,如果各位猿友觉得有所收获,就点个推荐或者留言激励下LZ,如果觉得浪费了自己宝贵的时间,也可以发下牢骚. 好了,废话就不多说了,现在就开始咱们的并发之旅吧. 并发编程的简单分类 并发常见的编程场景,一句话概括就是,需要协调多个线程之间的协作,已保证程序按照自己原本的意愿执行.那么究竟应该如何协调多个线程? 这个问题比较宽泛,一般情况下,我们按照方式的纬度

C++11 之 并发编程 (一)

未来芯片制造,如果突破不了 5nm 极限,那么在一段时间内 CPU 性能的提升,会依赖于三维集成技术,将更多的 CPU 核集成在一起,使得多核系统越来越普遍. 以前所谓的 C++ 多线程,一是受限于平台,多借助于封装好的 APIs 来完成,例如:POSIX threads,Windows threads 等:二是受限于单核系统,本质上都是“伪多线程”,通过线程的调度,使得单核系统可进行任务的切换,造成多线程的假象. 新的 C++11 标准,在语言层面上实现了多线程,其库中提供了相关组件,使得跨平

Linux多线程编程小结

 Linux多线程编程小结 前一段时间由于开题的事情一直耽搁了我搞Linux的进度,搞的我之前学的东西都遗忘了,非常烦躁的说,如今抽个时间把之前所学的做个小节.文章内容主要总结于<Linux程序设计第3版>. 1.Linux进程与线程 Linux进程创建一个新线程时,线程将拥有自己的栈(由于线程有自己的局部变量),但与它的创建者共享全局变量.文件描写叙述符.信号句柄和当前文件夹状态. Linux通过fork创建子进程与创建线程之间是有差别的:fork创建出该进程的一份拷贝,这个新进程拥有自己的