进程、线程与协程面试详解

进程与线程以及协程之间的区别

进程(Process)、线程(Thread)和协程(Coroutine)都是程序执行流的不同抽象层次。他们在处理任务并发性、资源占用和上下文切换等方面有所不同。

  1. 进程:进程是操作系统进行资源分配和调度的基本单位,也是进程间进行隔离的单位。每个进程有自己独立的内存空间,一个进程内部可以包含多个线程。进程的上下文切换代价比较大。

  2. 线程:线程是程序执行流的最小单位,线程是在进程的基础上分出的更小的运行单位。一个进程可以拥有多个线程,这些线程共享进程的资源,比如内存、文件句柄等。线程间的上下文切换代价比进程小。

  3. 协程:协程是一种用户态的轻量级线程,也被称为微线程或纤程。协程的调度完全由用户控制。协程有自己的寄存器上下文和栈。当协程调度切换时,可以直接保存当前协程的状态,然后直接恢复另一个协程的状态,避免了内核态和用户态之间的切换,性能开销相较于线程更小。

关键区别:

  • 进程有自己独立的地址空间,每个进程至少有一个线程。他们的关系是:一个进程在其执行过程中可以产生一个或多个线程。

  • 线程是进程的一部分,同一个进程下的线程共享该进程的内存空间和资源,线程之间可以直接通讯,线程的切换开销小于进程的切换开销。

  • 协程是一种更轻量级的线程,它不是被操作系统内核所管理,而完全由程序所控制(也就是在用户态执行)。这意味着,在协程的切换中,几乎不需要切换内核状态,没有额外的系统开销,可以做到极高的并发处理能力。不过,协程的缺点是需要程序员自己进行调度,可能会使程序设计变得复杂。

这些概念都是为了解决计算机并发问题的,不同的方法适用于不同的情况,需要根据具体需求进行选择。

进程间的通信方式详解

推荐看这一篇:进程间的通信方式

这里的共享内存区是在用户空间吗?

是的,共享内存区域位于用户空间。共享内存是在两个或更多的进程之间共享和访问一段可以相互读写的内存区域,这种方式的通信非常快,因为数据不需要在进程之间复制。所有的共享内存进程都可以直接访问共享内存区域,在内存中的数据可以被任何一个拥有这块内存访问权的进程访问和改变。

共享内存只是内核创建的一个内存区域,而进程可以将这块内存连接到自己的地址空间,就像一块内存被多个进程共享一样。在物理内存中只有一份,但在多个进程的虚拟地址空间中都有映射。这就是所谓的"共享"。所以在这个层面上讲,共享内存是存在于用户空间的。

然而,应该注意,管理共享内存的工作(例如创建、连接、断开连接、删除等)是由内核来完成的,这部分工作是在内核空间进行的。但是,一旦共享内存区被一个进程连接到其地址空间,该进程就可以像访问普通内存一样来访问这个共享内存区,这是在用户空间进行的。

线程池

推荐查看这篇文章:
线程池

单机线程是指单线程池的情况吗

单机线程池执行断电了应该怎么处理?我们可以对正在处理和阻塞队列的任务做事务管理或者对阻塞队列中的任务持久化处理,并且当断电或者系统崩溃,操作无法继续下去的时候,可以通过回溯日志的方式来撤销正在处理的已经执行成功的操作。然后重新执行整个阻塞队列。也就是:阻塞队列持久化;正在处理任务事务控制;断电之后正在处理任务的回滚,通过日志恢复该次操作;服务器重启后阻塞队列中的数据再加载。

答:在计算机科学中,"单机"通常指的是单个的计算机系统或服务器,与分布式系统或集群系统相对。而"线程池"是一种多线程处理形式,它创建并管理多个线程,等待主线程分配可并行处理的任务(这些任务通常被提交到阻塞队列)。当一个线程完成任务处理后,它将返回线程池成为空闲状态,可以被再次使用。

所以,"单机线程池"应该指的是在单个计算机系统或服务器上运行的线程池。这并不一定是单线程的情况,线程池通常包含多个线程,以便并行处理多个任务。

你所描述的处理策略(阻塞队列的持久化,正在处理任务的事务控制,断电后正在处理任务的回滚以及服务器重启后阻塞队列中的数据再加载等)是在处理这种线程池中可能出现的故障,特别是在系统断电或崩溃的情况下,确保任务能够被正确地处理和恢复。

谈一谈CountDownLatch/CyclicBarrier/Semaphore用法

CountDownLatch,CyclicBarrier 的区别

CountDownLatchCyclicBarrier 都是 Java 并发编程中的同步辅助类,都可以控制线程等待其它线程完成任务。然而,它们在使用和功能上有一些重要的区别:

  1. 使用方式的区别

    • CountDownLatch 是一次性的,计数器的值只能在构造方法中设定,一旦设定后就不能被重置。当计数器被减到0后,所有在 CountDownLatch 上等待的线程会被唤醒。之后无法重复使用这个 CountDownLatch 对象。

    • CyclicBarrier 的计数器可以被重置,因此它可以被多次使用。当计数器的值被减至0时,所有等待的线程都会被唤醒,然后 CyclicBarrier 将自动重置到初始的计数值。

  2. 功能上的区别

    • CountDownLatch 的主要功能是允许一个或多个线程等待其他线程完成操作。例如,主线程等待其他子线程全部完成任务。

    • CyclicBarrier 的主要功能是让一组线程到达一个同步点后再同时继续执行。例如,一组线程需要相互等待,直到所有线程都准备好再一起继续执行。

  3. 操作上的区别

    • CountDownLatch 的计数器只能用 countDown 方法减少,而不能递增。

    • CyclicBarrier 的计数器在通过 await 方法减少到0后,会自动重置为初始值。

这两个工具类的选择取决于具体的使用场景,需要根据问题的实际需求来选择最适合的辅助类。

分别给几个使用的例子

当然,我会给出每个类使用的一个示例。

  1. CountDownLatch示例

假设我们正在建造一个复杂的多部件产品。产品的最后一步是将所有部件集中在一起,但这只能在所有部件都准备好之后才能完成。我们可以使用CountDownLatch来实现这一点。

int PART_COUNT = 10;
CountDownLatch latch = new CountDownLatch(PART_COUNT);

for(int i=0; i<PART_COUNT; i++){
    
    
    new Thread(() -> {
    
    
        System.out.println("部件"+Thread.currentThread().getId()+"已准备好");
        latch.countDown();  // 每准备好一个部件,计数减1
    }).start();
}

try {
    
    
    latch.await();  // 主线程等待所有部件准备好
} catch (InterruptedException e) {
    
    
    e.printStackTrace();
}

System.out.println("所有部件准备完毕,开始组装产品!");
  1. CyclicBarrier示例

假设有一个4人团队需要进行一项任务,但是他们需要在任务的每个阶段都聚在一起,只有当所有的成员都准备好了,才能进入下一阶段。我们可以使用CyclicBarrier来实现这个过程。

int TEAM_SIZE = 4;
CyclicBarrier barrier = new CyclicBarrier(TEAM_SIZE);

for(int i=0; i<TEAM_SIZE; i++){
    
    
    new Thread(() -> {
    
    
        try {
    
    
            System.out.println("队员"+Thread.currentThread().getId()+"已准备好");
            barrier.await();  // 等待所有队员准备好
            System.out.println("所有队员都已准备好,队员"+Thread.currentThread().getId()+"开始执行任务...");
            barrier.await();  // 等待所有队员完成当前阶段任务
            System.out.println("队员"+Thread.currentThread().getId()+"完成了任务...");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }).start();
}
  1. Semaphore示例

假设有10个人在银行排队使用3台ATM机,可以使用Semaphore来限制同时能够使用ATM机的人数。

int ATM_MACHINE = 3;
Semaphore semaphore = new Semaphore(ATM_MACHINE);

for(int i=0; i<10; i++){
    
    
    new Thread(() -> {
    
    
        try {
    
    
            semaphore.acquire();  // 获取ATM机
            System.out.println("顾客"+Thread.currentThread().getId()+"开始使用ATM机...");
            Thread.sleep(2000); // 使用ATM机的时间
            System.out.println("顾客"+Thread.currentThread().getId()+"结束使用ATM机...");
            semaphore.release();  // 使用完毕,释放ATM机
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }).start();
}

在每个示例中,我们都用同步辅助类来控制并发和同步。

猜你喜欢

转载自blog.csdn.net/yxg520s/article/details/131761557