PYTHON专题-(10)基操之我要玩并发

什么是并发?

  • 并发指的是两个或多个事件在同一时间间隔内发生。在计算机科学中,并发通常指的是一个程序同时执行多个独立的任务。这些任务可以同时进行,而不会相互干扰或阻塞彼此。并发可以提高程序的执行效率和资源利用率,但也需要考虑并发控制和同步问题。

并发操作主要有哪些? 

并发操作主要有以下几种:

  1. 同步操作:在多个线程或进程中通过互斥锁、信号量等机制实现对共享资源的同步访问,保证其一致性和完整性。

  2. 异步操作:多个任务可以独立运行,不需要等待其他任务的完成。通过回调函数、事件驱动等方式实现。

  3. 并行操作:同时进行多个任务,利用多核处理器或多台机器的计算资源,加速任务的完成速度。

  4. 分布式操作:将一个任务分解为多个子任务,在不同的机器或网络节点上并行执行。

  5. 互斥操作:通过锁、信号量等机制实现对共享资源的互斥访问,保证同一时间只有一个线程或进程能够访问该资源。

  6. 条件变量操作:通过条件变量等机制实现线程之间的通信和协作,如等待、唤醒、通知等操作。

  7. 线程池操作:通过线程池管理线程的创建、销毁和复用,提高多线程程序的性能和可维护性。

  8. 异常处理操作:处理多线程或多进程中可能出现的异常情况,保证程序的稳定性和可靠性。

进程和线程是什么?

  • 进程(Process)是指在计算机中正在运行的程序实例。每个进程都有自己的地址空间、数据和代码段以及其他资源,它们相互独立运行,不会互相干扰。
  • 线程(Thread)是在进程内部执行的一个执行单元。与进程不同的是,多个线程可以共享同一个进程的资源,包括地址空间、文件描述符、设备等。线程是进程的一个实体,一个进程可以有多个线程。
  • 线程是进程的一部分,而进程则是操作系统分配资源的最小单位。一个进程可以包含多个线程,这些线程共享同一进程的资源,并能并发执行。

线程和进程在操作系统中具有不同的属性和区别。

  1. 资源占用:进程之间相互独立,每个进程有自己的地址空间和资源,而线程共享同一进程的资源。
  2. 切换开销:线程之间的切换开销比进程小,因为线程共享同一进程的地址空间,切换时只需切换线程的执行上下文。
  3. 并发性:多个线程可以同时执行,实现并发操作,而进程则需要通过进程调度实现并发。
  4. 通信与同步:进程之间通常使用进程间通信(IPC)机制进行通信,而线程之间可以直接共享同一进程的内存空间,可以通过共享内存进行通信,并且同步问题较容易解决。
  5. 独立性:进程之间相互独立,互不影响,而线程之间共享同一进程的资源,一个线程崩溃可能影响整个进程的稳定性。

什么是多进程和多线程?  

  • 多进程是指在操作系统层面上同时运行多个独立的进程。每个进程有独立的内存空间,程序计数器和其他资源。多进程之间相互独立,彼此不会影响。每个进程都有自己的执行流,可以同时执行多个任务。
  • 多线程是指在同一个进程内启动多个线程来执行任务。多个线程共享同一个进程的内存空间,程序计数器和其他资源。多线程之间可以共享数据,彼此之间可以进行通信和协作。多线程可以实现任务的并发执行。

区别:

  • 内存占用:多进程需要分配独立的内存空间,而多线程共享同一内存空间,所以多线程的内存占用较小。
  • 切换开销:多进程切换时需要切换上下文环境,开销较大;而多线程切换时只需要切换线程的上下文环境,开销较小。
  • 通信方便程度:多进程通信需要使用进程间通信的方式,如管道、消息队列等;而多线程通信直接共享进程的内存空间,通信更方便。
  • 编程模型:多进程编程需要考虑进程之间的同步和通信,而多线程编程需要考虑线程的同步和锁机制。

选择多进程还是多线程取决于具体的应用场景和需求。

Python怎么实现多进程 ?

  • Python中可以使用multiprocessing模块来实现多进程。multiprocessing模块提供了一个Process类,可以用来创建新的进程。
    import multiprocessing
    
    def worker(num):
        """子进程的任务"""
        print(f'Worker {num} is running')
    
    if __name__ == '__main__':
        # 创建4个子进程
        processes = []
        for i in range(4):
            p = multiprocessing.Process(target=worker, args=(i,))
            processes.append(p)
            p.start()
    
        # 等待子进程结束
        for p in processes:
            p.join()
    
  • 在这个例子中,我们定义了一个worker函数作为子进程的任务。然后使用multiprocessing.Process类创建4个子进程,并将worker函数作为进程的任务。调用start方法启动进程,调用join方法等待子进程结束。
  • 注意,在Windows系统中,multiprocessing模块的代码必须位于if __name__ == '__main__':条件判断内,以避免创建子进程时出现递归调用的问题。
  • 除了使用multiprocessing模块外,还可以使用concurrent.futures模块来实现多进程。concurrent.futures模块提供了一个ProcessPoolExecutor类,可以用来创建进程池,简化多进程编程的过程。

什么是子进程?

  • 子进程是指在一个父进程的控制下被创建的新进程。子进程复制了父进程的所有属性和代码,并在父进程的地址空间中执行。父进程可以创建多个子进程,每个子进程都有一个唯一的进程ID。子进程通常用于并发执行多个任务,实现进程间的并行处理。

什么是进程池Pool?

  • 进程池(Pool)是一种并发编程的技术,用于管理和重用一组进程,以便并行执行任务。进程池由一组预先创建的进程组成,这些进程可以从一个任务队列中获取任务,并在完成任务后将结果返回给调用者。
  • 进程池通常由一个主线程或进程创建,并在启动时创建指定数量的工作进程。工作进程可以同时处理多个任务,当一个任务完成后,工作进程可以立即获取并开始执行下一个任务。这样,可以减少创建和销毁进程的开销,并将重复利用进程提高效率。
  • 进程池的使用可以简化并发编程的复杂性,特别是在涉及大量计算或IO密集型任务时。通过使用进程池,可以提高任务的响应速度和整体性能。
  • 在Python中,可以使用multiprocessing模块中的Pool类来创建和管理进程池。

Python怎么使用multiprocessing模块中的Pool类 ?

  • 使用multiprocessing模块中的Pool类可以方便地实现并行计算。Pool类提供了一种简单的方式来并行地执行多个函数。
    import multiprocessing
    
    # 定义一个函数,用于计算平方
    def square(x):
        return x**2
    
    if __name__ == '__main__':
        # 创建一个Pool对象,指定最大进程数为4
        pool = multiprocessing.Pool(processes=4)
        
        # 使用Pool的map方法来并行地计算列表中每个元素的平方
        result = pool.map(square, [1, 2, 3, 4, 5])
        
        # 打印结果
        print(result)
    
        # 关闭Pool,表示不再接受新的任务
        pool.close()
        
        # 等待所有子进程执行完毕
        pool.join()
    
  • 在上述代码中,首先定义了一个函数square,用于计算一个数的平方。然后在if __name__ == '__main__':条件下创建了一个Pool对象,指定最大进程数为4。使用Pool的map方法并行地计算了列表[1, 2, 3, 4, 5]中每个元素的平方。最后打印了结果。
  • 注意,为了避免在Windows平台上出现死锁问题,必须将创建Pool对象的代码放在if __name__ == '__main__':条件下执行。

什么是进程间通信?

  • 进程间通信(Inter-Process Communication,简称IPC)是指操作系统或者计算机系统中,不同进程之间进行数据或者信息交换的机制和方式。进程是运行中的程序的实例,每个进程都有自己的内存空间,独立执行。进程间通信允许不同的进程共享数据,发送和接收消息,协调任务等。

进程间通信的方式有多种,主要包括以下几种:

  1. 管道(Pipe):管道是一个单向的通信通道,在父进程和子进程之间传递数据。

  2. 命名管道(Named Pipe):命名管道也是一个单向的通信通道,但是可以在不相关的进程之间进行通信。

  3. 共享内存(Shared Memory):共享内存允许不同进程在同一个物理内存区域中进行读写操作,实现高效的数据共享。

  4. 信号量(Semaphore):信号量是一种用于进程同步的机制,可以控制共享资源的访问。

  5. 消息队列(Message Queue):消息队列是一个消息的链表,进程可以将消息放入队列中,其他进程可以从队列中读取消息。

  6. 套接字(Socket):套接字可以在网络上进行进程间通信,常用于不同计算机之间的通信。

不同的进程间通信方式适用于不同的场景和需求,开发者可以根据具体的应用场景选择合适的进程间通信方式。

怎么实现进程间通信?

  • 管道(Pipe):使用multiprocessing模块中的Pipe函数可以创建一个管道对象,用于在两个进程之间传递数据。其中一个进程作为发送方,将数据写入管道,另一个进程作为接收方,从管道中读取数据。
    from multiprocessing import Process, Pipe
    
    def sender(conn):
        # 发送数据
        conn.send('Hello')
    
    def receiver(conn):
        # 接收数据
        data = conn.recv()
        print(data)
    
    if __name__ == '__main__':
        # 创建管道对象
        parent_conn, child_conn = Pipe()
    
        # 创建发送进程
        sender_process = Process(target=sender, args=(parent_conn,))
        # 创建接收进程
        receiver_process = Process(target=receiver, args=(child_conn,))
    
        # 启动进程
        sender_process.start()
        receiver_process.start()
    
        # 等待进程结束
        sender_process.join()
        receiver_process.join()
    
  • 队列(Queue):使用multiprocessing模块中的Queue类可以创建一个进程安全的队列,用于在多个进程之间传递数据。其中一个进程作为发送方,调用put方法将数据放入队列,另一个进程作为接收方,调用get方法从队列中取出数据。
    from multiprocessing import Process, Queue
    
    def sender(queue):
        # 发送数据
        queue.put('Hello')
    
    def receiver(queue):
        # 接收数据
        data = queue.get()
        print(data)
    
    if __name__ == '__main__':
        # 创建队列对象
        queue = Queue()
    
        # 创建发送进程
        sender_process = Process(target=sender, args=(queue,))
        # 创建接收进程
        receiver_process = Process(target=receiver, args=(queue,))
    
        # 启动进程
        sender_process.start()
        receiver_process.start()
    
        # 等待进程结束
        sender_process.join()
        receiver_process.join()
    
  • 共享内存(Shared Memory):使用multiprocessing模块中的ValueArray类可以创建共享内存,多个进程可以直接读写这块内存,实现数据共享。
    from multiprocessing import Process, Value, Array
    
    def writer(shared_value, shared_array):
        # 写入共享内存
        shared_value.value = 123
        shared_array[0] = 456
    
    def reader(shared_value, shared_array):
        # 读取共享内存
        value = shared_value.value
        array_value = shared_array[0]
        print(value, array_value)
    
    if __name__ == '__main__':
        # 创建共享内存对象
        shared_value = Value('i')
        shared_array = Array('i', range(10))
    
        # 创建写入进程
        writer_process = Process(target=writer, args=(shared_value, shared_array))
        # 创建读取进程
        reader_process = Process(target=reader, args=(shared_value, shared_array))
    
        # 启动进程
        writer_process.start()
        reader_process.start()
    
        # 等待进程结束
        writer_process.join()
        reader_process.join()
    
  • 套接字(Socket):使用socket模块可以在网络中传递数据,也可以在同一台机器上不同的进程之间传递数据。通过创建套接字并使用bind方法绑定到本机的一个端口,然后使用sendrecv方法进行数据传输。
    import socket
    import multiprocessing
    
    def sender():
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            # 绑定到本机的一个端口
            s.bind(('localhost', 8888))
            s.listen(1)
    

管道和队列的区别?

管道和队列都是用于进程间通信的机制,但它们有一些区别。

  1. 数据结构:管道是一种特殊的文件,具有固定的读端和写端,进程通过读端从管道中读取数据,通过写端向管道中写入数据。而队列是一种数据结构,有头部和尾部,元素按照一定的顺序放入和取出。

  2. 通信方式:管道是一种半双工的通信方式,即同一时刻只能有一个进程进行读或写操作。而队列可以是单向的或双向的,可以同时有多个进程进行入队或出队操作。

  3. 通信对象:管道只能用于具有亲缘关系的进程间通信,即父子进程或兄弟进程。而队列可以用于任意进程间通信,不受关系限制。

  4. 数据传输:管道通常是字节流的形式,不保留边界信息,进程需要自行解析数据。而队列通常是以消息的形式进行传输,每个消息包含一个完整的数据包。

  5. 复杂性:队列通常比管道更复杂,需要维护队列的头部和尾部指针,以及处理并发访问的问题。而管道相对简单,只需处理读写两个操作。

总的来说,管道适用于具有亲缘关系的进程间通信,对于简单的数据传输任务较为合适;而队列则适用于任意进程间通信,对于复杂的消息传输场景更为适用。

什么是套接字?

  • 套接字(Socket)是计算机网络中一种通信机制,它允许在不同主机间的进程进行通信。套接字提供了一个抽象层,使得进程可以通过发送和接收数据来进行通信,无需关注底层网络细节。
  • 套接字可以用于实现不同的通信协议,如TCP(传输控制协议)和UDP(用户数据报协议)。在使用套接字进行通信时,通常有一个客户端和一个服务器端。客户端通过创建一个套接字来发起连接请求,而服务器端通过监听一个套接字来接受连接请求。
  • 套接字通常使用IP地址和端口号来标识通信的双方。IP地址用于找到目标主机,而端口号用于找到目标进程。在进行通信前,双方需要协商好使用的协议和端口号。
  • 套接字的使用方式可以分为阻塞和非阻塞。在阻塞模式下,套接字会一直等待直到有数据可以读取或写入。在非阻塞模式下,套接字会立即返回结果,并根据返回值来判断是否有数据可读取或写入。

Python怎么实现多线程 ? 

  • 在Python中,可以使用threading模块来实现多线程。
    import threading
    
    # 线程1的执行函数
    def thread1_func():
        print("Thread 1 is running")
    
    # 线程2的执行函数
    def thread2_func():
        print("Thread 2 is running")
    
    # 创建线程对象
    thread1 = threading.Thread(target=thread1_func)
    thread2 = threading.Thread(target=thread2_func)
    
    # 启动线程
    thread1.start()
    thread2.start()
    
    # 等待线程执行完毕
    thread1.join()
    thread2.join()
    
    print("All threads have finished")
    
  • 在这个例子中,我们首先定义了两个函数thread1_functhread2_func,它们将作为线程1和线程2的执行函数。然后,我们使用threading.Thread类创建了两个线程对象thread1thread2,并将执行函数分别传递给它们。最后,我们调用start方法启动线程,然后调用join方法等待线程执行完毕。
  • 需要注意的是,Python中的多线程并不是真正的并行执行,而是通过线程切换来实现的。这是由于Python解释器的全局锁(GIL)的限制。如果需要实现真正的并行执行,可以考虑使用multiprocessing模块来创建多个进程。

什么是全局锁(GIL)? 

  • 全局锁(GIL)是指在Python解释器中的一种机制,用于确保在多线程环境下同一时刻只有一个线程在执行Python字节码。这意味着在多线程环境下,无论有多少个CPU核心,同一时刻只能有一个线程在执行Python代码。
  • GIL的存在是为了简化Python解释器的实现,因为它可以避免多线程环境下对共享数据的竞争和冲突问题。然而,由于GIL的存在,Python的多线程并不是真正的并行执行,而是通过在不同线程之间切换来模拟并发执行。这也是为什么Python在处理CPU密集型任务时效率较低的原因。但对于I/O密集型任务,Python的多线程仍然可以带来一定程度的性能提升。

什么是CPU密集型任务和I/O密集型任务?

  • CPU密集型任务是指需要大量CPU计算资源的任务,例如高性能计算、科学模拟、图像处理等。这类任务主要依赖于CPU处理能力,对CPU的计算速度要求较高,通常不涉及大量的数据读写操作。
  • I/O密集型任务是指需要大量I/O操作的任务,包括文件读写、网络通信、数据库访问等。这类任务主要涉及数据的输入输出操作,对CPU的计算能力要求相对较低,但对I/O设备的响应速度要求较高,尤其是涉及大量的数据读写操作。

什么是ThreadLocal? 

  • 在Python中,ThreadLocal是一个线程局部变量,可以让每个线程都拥有自己独立的变量副本,互相之间不会干扰。
  • ThreadLocal通常用于解决多线程环境下数据共享的问题。在多线程环境下,如果多个线程共享同一个变量,可能会导致数据不一致或竞争条件的发生。而使用ThreadLocal可以为每个线程创建一个独立的变量副本,保证了每个线程都操作自己的变量,从而避免了这些问题。
  • 通过ThreadLocal,每个线程都可以独立地操作自己的变量副本,而无需关心其他线程的操作。这样可以提高多线程程序的效率和安全性。
  • 在Python中,可以使用threading模块的ThreadLocal类来创建ThreadLocal变量。可以通过ThreadLocal的set()方法设置变量的值,通过get()方法获取变量的值。

在python中,多进程和多线程的区别和应用场景是什么? 

区别:

  1. 多进程是指在操作系统中同时执行多个独立的进程,每个进程有自己独立的内存空间,它们之间没有共享的内存;而多线程是指在同一个进程中执行多个独立的线程,它们共享相同的内存空间。
  2. 多进程的切换开销较大,因为切换进程需要保存和恢复进程的上下文,而多线程的切换开销较小,因为切换线程只需要保存和恢复线程的上下文。
  3. 多进程可以更好地利用多核处理器的优势,因为每个进程可以在不同的核上并行执行;而多线程不能同时在多个核上并行执行,因为操作系统会将多个线程分配到同一个核上执行。

适用场景:

  1. 多进程适合用于CPU密集型任务,即需要大量的计算资源的任务,因为多个进程可以在多个核上同时进行计算,从而提高整体的运算速度。
  2. 多线程适合用于I/O密集型任务,即需要频繁进行I/O操作的任务,比如网络通信、文件读写等,因为在I/O操作时,线程可以释放CPU资源,从而充分利用CPU的时间片,提高整体的吞吐量。
  3. 在需要进行大规模并行计算的场景中,可以同时使用多进程和多线程,即将任务分为多个子任务,每个子任务使用一个进程,而每个进程内部使用多个线程来进行计算,以充分利用多核和多线程的优势,提高计算效率。

什么是协程?

  • 协程是一种并发编程的技术,也被称为轻量级线程。它是一种在单个线程中实现并发的方式,不同于传统的线程或进程。协程可以在程序中进行切换执行,即在一个协程执行过程中,可以暂停执行并切换到另一个协程中执行,然后再返回到原来的协程继续执行。
  • 协程的一个主要特点是可以在执行过程中保存上下文,包括变量的值、指令的位置等等,使得在切换到其他协程执行后可以恢复到之前的状态。这样可以避免了多线程中频繁地进行上下文切换所带来的开销,从而提高了程序的性能。
  • 协程的应用场景包括异步编程、高并发处理、事件驱动等领域。在异步编程中,协程可以用于实现协作式多任务处理,使得多个任务可以按照一定的顺序依次执行,而不是同时执行。在高并发处理中,协程可以帮助实现高效的请求处理,提高系统的吞吐量和响应速度。在事件驱动中,协程可以用于处理事件的触发和响应,提供更加灵活的事件处理方式。总之,协程是一种强大的并发编程技术,可以帮助提高程序的性能和可维护性。

为什么需要协程?

协程是一种轻量级的并发编程方式,它可以在一个线程中实现多个任务的并发执行。相比于线程或进程,协程有以下优势:

  1. 轻量级:协程的创建和切换开销很小,不像线程和进程需要操作系统的支持和上下文切换的开销,因此可以高效地使用系统资源。

  2. 高并发:由于协程是在单个线程中运行,因此可以同时运行多个协程,实现高并发的效果。而线程或进程的数量受限于系统的硬件资源,不能无限增加。

  3. 高效率:由于协程是在单个线程中切换执行的,避免了线程切换的开销和同步的复杂性,因此可以提高程序的运行效率。

  4. 简化编程模型:协程通过使用异步操作和事件循环,解决了传统多线程编程中的并发问题。它可以将并发任务拆分为多个独立的协程,每个协程只处理自己的任务,简化了程序的编写和维护。

  5. 更低的资源消耗:由于协程的创建和切换开销很小,因此可以创建更多的协程来处理任务,而不像线程或进程那样受限于系统资源。这样可以更充分地利用系统资源,提高任务的处理能力。

因此,协程在并发编程中具有重要的作用,可以提高程序的运行效率和并发处理能力,简化编程模型,减少资源消耗。

怎么实现协程?

  • 在Python中,可以使用asyncio模块来实现协程。
    import asyncio
    
    async def foo():
        print('Foo')
        await asyncio.sleep(1)
        print('End Foo')
    
    async def bar():
        print('Bar')
        await asyncio.sleep(2)
        print('End Bar')
    
    async def main():
        task1 = asyncio.create_task(foo())
        task2 = asyncio.create_task(bar())
    
        await task1
        await task2
    
    asyncio.run(main())
    
  • 在上面的例子中,foo()bar()函数都是协程,使用async关键字进行定义。这些协程函数可以使用await关键字来等待其他协程的运行结果。在main()函数中,我们通过asyncio.create_task()函数创建了两个协程任务,并使用await关键字等待它们完成。最后,通过调用asyncio.run()函数来运行main()函数,从而启动协程的执行。
  • 在上面的例子中,foo()bar()协程函数分别打印一些信息,并使用await asyncio.sleep()来模拟协程的执行时间。

什么是aiohttp? 

  • aiohttp是一个用于构建基于Python的异步Web应用程序的库。它是基于Python的asyncio库开发的,为开发者提供了构建高性能、可伸缩、异步Web应用程序的工具。aiohttp提供了HTTP客户端和服务器功能,支持HTTP/1.1和WebSocket协议,并具有请求和响应处理、路由、中间件、模板等功能。通过使用aiohttp,开发者可以轻松地构建基于异步IO的高性能Web应用程序。

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值