并发编程【2】

 01.什么是僵尸进程,孤儿进程

       僵尸进程是指在进程已经终止但是其父进程尚未终止信息(退出状态码)的情况下。保留在进程表中的进程。僵尸进程不占用实际的系统资源,但会占用一个进程ID,并且会在系统中产生垃圾。

        孤儿是指在网络中失去父进程(创建它的进程)却继续运行的子进程。通常是由于父进程异常退出或未正确管理子进程而导致。它就会并由init进程来处理孤儿进程的退出状态。在从进程表中删除孤儿进程的记录。

02.什么是互斥锁,优点缺点

        什么是互斥锁:是一种用于多线程编程中的同步机制,用于保护共享资源,确保在同一时间只有一个线程可以访问该资源。

优点:

  1. 确保数据完整性:

  2. 简单易用:使用互斥锁可以方便地实现对共享资源的互斥访问,提供了一种简单可靠的同步方法。

  3. 高效性能:

缺点:

  1. 死锁风险:如果在设计和使用互斥锁时不小心,可能会导致死锁问题,即多个线程相互等待对方释放锁而无法继续执行。

  2. 性能开销:在互斥锁的保护下,只有一个线程能够访问共享资源,其他线程需要等待,这可能引入一定的性能开销。

  3. 可能存在优先级反转:当高优先级的线程获取到互斥锁时,可能会阻塞低优先级的线程,导致优先级反转问题。

03.什么是守护进程,如何实现

        守护进程(Daemon)是在操作系统中以后台形式运行的一类特殊进程。它通常在系统启动时启动,并且在整个系统运行期间持续运行,独立于终端会话。

安包daemon

python daemon.py

04.僵尸和孤儿哪个危害大,为什么

        僵尸进程会占用大部分的资源,并且这部分资源没人来处理

        孤儿进程虽然父进程没了,但是 init 进程会将这部分资源释放掉

程序:一堆数据和代码

进程:跑起来的程序

线程:在进程内开始的进程

【一】进程

【1】什么是进程

        进程就是计算机中正在运行的程序的实例。它是操作系统进行任务调度和资源分配的基本单位。每个进程拥有自己独立的内存空间,包括代码、数据和堆栈等信息。

而复杂执行任务则是CPU

【2】进程的特点

  • 独立性:每个进程都是独立运行的实体,相互之间不会干扰或者影响。它们有各自的内存空间。

  • 并发性:多个进程可以同时运行在多个核处理器上,实现并行计算和提高系统的吞吐量。

  • 随机性:操作系统决定进程的调度顺序,根据调度策略分配CPU时间片给不同的进程,使得它们交替执行,给用户带来了随机性的体验。

  • 可抢占性:操作系统可以暂停一个进程的执行,并将CPU资源分配给其他进程。当资源可用时,被暂停的进程可以继续执行。

  • 共享性:进程之间可以共享资源,比如打开的文件、网络连接等。但是需要通过同步机制来确保数据的一致性和安全性。

        进程是多任务系统的基础,它使得计算机能够同时运行多个程序,并实现并发执行。操作系统通过进程管理群来隔离和调度进程,分配计算机资源,确保系统的稳定性和效率。

【3】进程与程序的区别

  • 程序仅仅只是一堆代码而已

  • 而进程指的是程序的运行过程。

【4】单进程

        单进程指在操作系统中只有一个进程在运行。这意味着系统一次只能执行一个任务或程序,没有并发的能力。当一个任务或程序执行时,其他任务或程序需要等待。

适用场景:

        单进程适用于一些简单的应用场景,如一些小型的工具或者脚本,不需要同时处理多个任务的情况。

【5】多进程

        多进程指早操作系统中同时运行多个独立的进程。每个进程拥有自己独立的资源和执行环境,可以并发地执行。多进程可以利用多核处理器的性能,提高系统的吞吐量和响应速度。

多进程的适用场景:

        适合用于需要同时处理多个任务或者程序的复杂应用场景,如并行计算、服务器处理、大规模数据处理等:

比较单进程和多进程的优缺点:

  • 单进程相对简单,适用于小规模、简单的应用场景、开销较小。

  • 多进程能够提高系统的并发处理能力,可以重充分利用核处理器的性能,但也会增加系统的开销和复杂性。

  • 单进程无法同时处理多个任务,可能会导致整个系统的响应速度变慢。

  • 多进程能够同时处理多个任务,并发执行,提高四天的性能和效率,但需要考虑进程通信和资源管理等问题。

【二】线程

【1】什么是线程

        线程是计算机科学中的一个重要概念,指的是在一个进程内执行的独立任务。线程是操作系统能够进行运算调度的最小单位。在现代操作系统中,每个进程可以拥有多个线程,这些现场共享进程的资源,同时有具有独立的执行流程。

【2】线程的特点

  • 轻量级:线程比进程消耗更少的资源,可以创建大量的线程而不会对系统造成重大的影响。

  • 共享内存:线程之间可以共享进程的内存空间,这使得线程之间的通信更加方便快捷。

  • 可以并发执行:多个线程可以同时执行,从而提高程序的运行效率。

  • 非独立调度和分配CPU时间:线程不能脱离进程单独运行,操作系统会将CPU时间分配给进程中的所有线程。

  • 状态切换开销小:相对进程而言,线程的状态切换开销较小,因为线程间共享了进程的资源,状态切换是只需要保存和恢复少量的上下文信息。

  • 安全性问题:由于线程共享的内存空间,因此需要采用同步机制来确保线程之间的数据访问安全。

【2】单线程

        单线程指的是程序只有一个主线程在执行。在单线程模型中,所有的任务按照顺序依次执行,每个任务必须等待前面的任务完成后才能执行。

适用场景:

一些简单的程序或者任务间没有明显的并发需求的场景。

【3】多线程

        进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是CPU上的执行单位。

【4】什么是多线程

        多线程是指早单个进程中同时运行多个线程的技术。线程是操作系统进行任务调度和资源分配的基本单位,它比进程更轻量级,可以共享进程的资源,如内存卡就、文件句柄等。多线程技术可以提高程序的并发性和响应能力,实现更高效的计算和资源利用。

【5】与单线程相比,多线程具有一下优缺点:

  • 更高的并发性:多个线程可以并行执行,从而提高程序的处理能力和响应速度。

  • 更高的资源利用率:多个线程可以共享进程的资源,避免了资源的重复占用和浪费。

  • 更灵活的编程模型:多线程使得程序具有更零下的结构和设计,可以更好地适应不同的需求。

缺点

  • 难以排查和调试问题

  • 数据同步和共享问题

  • 资源消耗

  • 程序设计复杂性增加

  • 并发控制和竞争条件

首先分析页面 ----> 就是要页面上的图片

页面上的图片----> 图片上的数据

图片上的数据 ----> 二进制数据

二进制数据 ----> 数据写入

【三】进程间通信

【1】什么是进程间通信

  • 进程间通信 是指两个或多个进程之间进行信息交换的过程。

  • 它是一种计算机编程技术,用于在不同进程之间共享数据和资源。

        是指在多进程系统中,不同进程之间进行数据交换和共享的一种机制。

【2】如何实现进程通信

  • 借助与消息队列,进程可以将消息放入队列中,然后由另一个进程从队列中取出。

  • 这种通信方式是非阻塞的,即发送进程不需要等待接收的响应即可继续执行。

  • multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

【3】什么是管道

        管道是一种半双工的通信方式,他可以在具有亲缘关系的父子进程或者兄弟进程之间通信。在Unix/Linux系统中, 可以使用pipe()函数创建管道,通过fork()系统调用创建子进程后,父子进程就可通过管道通信。

  • stdin、stdout和stderr是Python中三个内置文件对象,它们分别代表标志输入、标准输出和标准错。这些也可以作为管道使用。

  • 当我们在一个进程中使用read方法读取管道内的消息后买它进程将无法在获取该管道内的任何其他消息。

  • 因此,我们需要使用锁或者其他同步机制来确保能够正确地访问和修改共享资源。

【4】什么是队列(管道 + 锁)

        队列是一种常见的数据结构,用于在多线程或者多进程环境中实现线程见的安全通信和数据交换。队列可以看作是一个先进先出(FIFO) 的缓冲区,其中的袁术按照添加的顺序进行处理。

队列:先进先出

堆栈: 后进先出

        锁:是一种同步机制,用于确保在同一时刻只有一个线程可以访问被锁定的资源。在队列中,锁用于保护对共享数据的访问,防止多个现场同时修改数据造成的冲突和不一致。

        队列是一种线程安全的数据结构,它支持在多线程环境中高效地实现生产者-消费者模型。

【5】进程间通信的目的

  • 存是为了更好的取

  • 千方百计的存

  • 简单快捷的取

【四】队列的使用

        队列是一种常见的数据结构,用于实现先进先出的缓冲区。

queue模块: 可以使用queue.Queue类来创建一个队列对象,然后使用器提高的方法来进行元素的入列和出对操作。

  • put(item) :将元素 item 入队。

  • get() :从队列中出队返回一个元素。

  • empty() :判断队列是否为空。

  • full() :判断队列是否已满。

  • qsize() :获取队列中当前元素的个数。

  • q.get_nowait() : 队列无数据报错。

    q = queue.Queue()
    q.put(1)
    q.put(21,)
    ​
    print(q.get())
    print(q.get())
    print(q.empty())
    print(q.full())
    # 1
    # 21
    # True
    # False
    # popleft()方法从队列的头部弹出一个元素。
    from collections import deque

    deque是Python标准库 collections 模块中的双端队列实现。deque提高了与列表类似的操作,但在头部和尾部都能高效地进行插入和删除操作。可以在append()添加在队列尾部

    popleft()方法从队列的头部弹出一个元素。 ​

# popleft()方法从队列的头部弹出一个元素。

q = deque()
q.append(123)
q.append(456)
print(q.popleft())
print(len(q))
# 123
# 1

        第三方库 queue:除了标准库中的 queue 模块,还有一些第三方库也提供了更高级和更强大的队列实现。例如,queue 库是一个线程安全的队列实现,提供了更多的功能和选项,如优先级队列、固定大小队列等。

from queue import PriorityQueue
​
q = PriorityQueue()
q.put((2, 'Item 2'))
q.put((1, 'Item 1'))
print(q.get())  # 输出: (1, 'Item 1')
from multiprocessing import Process,Queue
# Queue队列的使用方法
def producer(q):
    for i in range(5):
        q.put(i)
        print(f'生产者:{i} 这是 生产者!')
​
​
def consumer(q):
    while True:
        item = q.get()
        if item is None:
            break
        print(f'消费者: {item} 这是消费者')
​
if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer, args=(q,))
    p2 = Process(target=consumer, args=(q,))
    p1.start()
    p2.start()
    p1.join() # 这就是解决消费者大于生产者的问题
    p2.join()
    # 生产者:0
    # 这是
    # 生产者!
    # 生产者:1
    # 这是
    # 生产者!
    # 生产者:2
    # 这是
    # 生产者!
    # 生产者:3
    # 这是
    # 生产者!
    # 消费者: 0
    # 这是消费者
    # 生产者:4
    # 这是
    # 生产者!
    # 消费者: 1
    # 这是消费者
    # 消费者: 2
    # 这是消费者
    # 消费者: 3
    # 这是消费者
    # 消费者: 4
    # 这是消费者

【五】进程通信(IPC机制)

【1】什么是IPC机制

  • IPC机制指进程间通信机制,进程间通信,进程与进程之间数据交互

【2】子进程与主进程之间通过队列进行通信

from multiprocessing import Process, Pipe
​
# 创建一个子进程函数
def child_process(conn):
    # 向管道写入消息
    conn.send('这是子进程发出的消息')
    print('子进程向管道写入了一条消息')
​
if __name__ == '__main__':
    # 【一】创建管道对象:注意这里是使用的 multiprocessing 里面的 Pipe
    parent_conn, child_conn = Pipe()
    # 【二】创建子进程:目标函数和参数带进去
    child_proc = Process(target=child_process, args=(child_conn,))
    # 【三】启动子进程
    child_proc.start()
    # 【四】主进程读取消息
    msg_from_child = parent_conn.recv()
    # 【五】查看消息
    print(msg_from_child)
    # 子进程向管道写入了一条消息
    # 这是子进程发出的消息

【六】生产者和消费者模型

生产者和消费者模型是一种常用的并发编程模型,它主要用于解决生产者和消费者之间的数据交互问题。

【1】生产者

生产者负责生成数据,并将数据放入共享的缓冲区。

【2】消费者

消费者则从缓冲区中获取数据进行处理。

import threading
import queue
import time
​
# 共享的缓冲区
buffer = queue.Queue(5)
​
# 退出标志位,初始为 False
exit_flag = False
​
# 生产者函数
def producer():
    while not exit_flag:
        # 模拟生产数据
        data = f'数据'
        print(f'生产者生产了:{data}')
        # 将数据放入缓冲区
        buffer.put(data)
        time.sleep(1)
​
# 消费者函数
def consumer():
    while not exit_flag or not buffer.empty():
        # 从缓冲区获取数据
        data = buffer.get()
        print(f'消费者消费了:{data}')
        # 模拟处理数据
        time.sleep(2)
        buffer.task_done()
​
if __name__ == '__main__':
    # 创建生产者线程
    producer_thread = threading.Thread(target=producer)
    # 创建消费者线程
    consumer_thread = threading.Thread(target=consumer)
​
    # 启动线程
    producer_thread.start()
    consumer_thread.start()
​
    # 等待用户输入,回车键退出
    
    input("按回车键退出程序\n")
​
    # 设置退出标志位为 True
    exit_flag = True
​
    # 等待线程结束
    producer_thread.join()
    consumer_thread.join()

小结:

【一】生产者和消费者模型总结

        生产者 : 生产数据 生产完所有数据以后要加一个标志位

        消费者 : 消费数据 从生产者生产的数据中拿出所有数据

【二】解决生产者和消费者不平稳

        一 Queue 在生产者生产数据的结尾 ---> 放一个标志位 消费者消费数据 ---> 消费到最后一个数据的时候,得到标志位知道生产的数据空了 --> 结束了 【三】解决生产者和消费者不平稳二 JoinableQueue

        在生产者生产数据的结尾 ---> 放一个标志位 join() 在消费者消费数据的 ----> 正产消费 ---> 在消费数据的结尾 task_done() ---> 自动检索上面生产者是否存在 join

你可能感兴趣的:(服务器,linux,网络)