线程和线程池

1.引入

1.1 进程的描述

  1. 进程的引入:在多道程序中,为了使程序并发执行,并且可以对并发执行的程序加以描述和控制,人们引入了“进程”的概念。为了使参与并发执行的程序都能够独立地执行,操作系统必须为之配置一个数据结构——进程控制块(PCB)。从而有了进程实体的概念——程序段,相关的数据段和PCB(又称为进程映像)。从而我们可以把传统OS中的进程定义为“是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。”
  2. 进程的定义:所以可以说,进程是一段可并发执行的具有独立功能的程序,是关于某个数据集上的一次执行过程,也是OS进行资源分配,调度和保护的基本单位。
  3. 程序并发执行所付出的时空开销:为使程序能够并发执行,系统必须进行以下操作:1)创建进程;2)撤销进程;3)进程切换。

1.2 线程的引入

       为了减少进程在并发执行中的所付出的时空开销,使OS具有更好的并发性,提高CPU利用率。我们引入线程,某种意义上,可以把线程看作是“不具备单独的地址空间的进程”。

        以下是线程和进程在6方面的对比表:

对比方面进程线程
1. 调度的基本单位进程是操作系统进行调度的基本单位。每个进程都有独立的代码、数据和堆栈。线程是轻量级的调度单位,属于进程的一部分。线程共享进程的代码段和数据段,但有独立的堆栈和寄存器。
2. 并发性进程之间可以并发执行,但由于创建和上下文切换开销大,并发效率相对较低。线程之间可以并发执行,并且线程切换的开销较低,因此并发效率较高。
3. 拥有资源进程拥有独立的资源(如地址空间、文件句柄等),是资源分配的基本单位。线程不拥有资源,线程共享其所属进程的资源(如地址空间、文件句柄等)。
4. 独立性进程之间相互独立,进程有自己的地址空间,操作互不干扰。线程之间不完全独立,共享进程资源,因此线程之间的操作可能互相干扰,需要同步机制避免资源竞争问题。
5. 系统开销由于进程需要独立的资源和地址空间,创建、销毁以及上下文切换的开销较大。线程创建、销毁以及上下文切换的开销较小,因为线程共享进程资源,不需要分配独立的地址空间。
6. 支持多处理机系统进程可以分布在多个处理器上运行,但进程之间的通信需要通过操作系统提供的机制(如管道、消息队列等),效率较低。线程可以分布在多个处理器上运行,且线程之间的通信更高效,因为它们共享进程的地址空间和资源。

 1.3 异步任务

       '异步"——指的是时间上不同时刻发生的操作。在编程中,异步意味着发起一个任务后,无需立即等待任务完成,而是继续可以执行其他的工作。当任务完成时,会通过回调(callback),promise, 事件或其他机制通知程序。

       异步任务是指哪些无需程序立即等待完成的任务。例如:网络请求等。

2.线程的实现

两种类型的线程实现:

(1)内核支持线程(KST)

        是在内核支持下执行的线程。无论是用户进程中的线程,还是系统中的线程,它们的创建、撤销和切换等操作都是依靠内核,在内核空间中实现的。在内核空间中还未每个内核支持线程设置了TCB(线程控制块),内核根据该TCB感知某线程的存在并对实施控制。

(2)用户级线程(ULT)

        用户级线程是仅存在于用户空间中的线程,无需内核支持。这种线程的创建与撤销,线程间的同步与通信功能,都无需利用系统调用实现。用户级线程的切换通常发生在一个应用程序的诸多线之间,同样无需内核支持。

以下是用户级线程和内核支持线程的区别,从多个角度进行详细分析:

对比角度用户级线程内核支持线程
1. 实现方式用户级线程完全由用户空间的线程库实现,操作系统内核对此不可见。内核支持线程由操作系统内核直接管理,线程的创建、调度和销毁都由内核负责。
2. 调度管理线程调度由线程库在用户空间完成,线程切换不需要内核的参与,因此调度开销较小。线程调度由操作系统内核完成,线程切换需要系统调用,因此调度开销较大。
3. 性能切换线程时仅需在用户空间完成上下文切换,无需进入内核态,因此性能较高。切换线程时需要内核态和用户态之间切换,存在一定的性能开销。
4. 系统支持不依赖操作系统的线程支持,跨平台性更强,但无法利用多处理器的并行能力。依赖操作系统的线程支持,能够充分利用多处理器实现真正的并行执行。
5. 阻塞操作如果一个用户级线程发生阻塞(如I/O操作),整个进程都会阻塞,因为操作系统无法识别线程的存在。如果一个内核支持线程阻塞,操作系统可以调度其他线程继续执行,不会阻塞整个进程。
6. 开销由于完全在用户空间实现,线程的创建、销毁和切换开销较小,无需系统调用。线程的创建、销毁和切换都需要系统调用,因此开销较大。
7. 并行能力由于内核只管理进程,无法识别用户级线程,因此即使在多处理器环境下,用户级线程也只能在一个处理器上执行(模拟并发)。内核支持线程可以在多个处理器上同时运行,能够实现真正的并行执行。
8. 开发灵活性用户级线程可以根据应用需求定制调度策略,灵活性高,但需要开发者自行处理阻塞问题。内核支持线程的调度完全由操作系统控制,开发者无需关心底层细节,易于编程,但灵活性较低。
9. 调试难度由于完全运行在用户空间,调试用户级线程较为困难,尤其是在遇到线程调度或阻塞问题时。内核支持线程由操作系统管理,调试工具较为完善,调试和诊断相对简单。
10. 典型应用场景适用于对性能要求高、线程数量多但线程阻塞较少的场景,例如科学计算中的并发模拟。适用于需要频繁阻塞操作或多处理器支持的场景,例如多线程服务器和高性能并发应用。

3. 线程池?

3.1 什么是线程池?

”A thread pool is a pool threads that can be "reused" to execute tasks, so that each thread may execute more than one task. A thread pool is an alternative to creating a new thread for each task you need to execute.“

       “​线程​”没什么好说的,是 CPU 调度的最小单位,也是操作系统的一种抽象资源。

       “​池​​”?水池装着水,线程池则是装着线程,是一种抽象的指代。

       抽象的来说,可以当做是一个池子中存放了一堆线程,​故称作线程池​​。简而言之,线程池是指代一组​预先创建的​、​可以复用的线程集合​。这些线程由线程池管理,用于执行多个任务而无需频繁地创建和销毁线程。

       这是一个典型的线程池结构。线程池包含一个任务队列,当有新任务加入时,调度器会将任务分配给线程池中的空闲线程进行执行。线程在执行完任务后会进入休眠状态,等待调度器的下一次唤醒。当有新的任务加入队列,并且有线程处于休眠状态时,调度器会唤醒休眠的线程,并分配新的任务给它们执行。线程执行完新任务后,会再次进入休眠状态,直到有新的任务到来,调度器才可能会再次唤醒它们。

        图中线程1 就是被调度器分配了任务1,执行完毕后休眠,然而新任务的到来让调度器再次将它唤醒,去执行任务6,执行完毕后继续休眠。

3.2 线程池的实现

3.2.1 辅助工具类
             在BS::thread_pool中一个关键的辅助工具类——multi_future,它扩展了std::vector<std::future<T>> ,提供了对多个异步任务结果的管理以及操作能力。
template <typename T>
class [[nodiscard]] multi_future : public std::vector<std::future<T>>
{
public:
    // Inherit all constructors from the base class `std::vector`.
    using std::vector<std::future<T>>::vector;

  
    [[nodiscard]] std::conditional_t<std::is_void_v<T>, void, std::vector<T>> get()
    {
        if constexpr (std::is_void_v<T>)
        {
            for (std::future<T>& future : *this)
                future.get();
            return;
        }
        else
        {
            std::vector<T> results;
            results.reserve(this->size());
            for (std::future<T>& future : *this)
                results.push_back(future.get());
            return results;
        }
    }

    
    [[nodiscard]] std::size_t ready_count() const
    {
        std::size_t count = 0;
        for (const std::future<T>& future : *this)
        {
            if (future.wait_for(std::chrono::duration<double>::zero()) == std::future_status::ready)
                ++count;
        }
        return count;
    }

    
    [[nodiscard]] bool valid() const noexcept
    {
        bool is_valid = true;
        for (const std::future<T>& future : *this)
            is_valid = is_valid && future.valid();
        return is_valid;
    }

   
    void wait() const
    {
        for (const std::future<T>& future : *this)
            future.wait();
    }

    
    template <typename R, typename P>
    bool wait_for(const std::chrono::duration<R, P>& duration) const
    {
        const std::chrono::time_point<std::chrono::steady_clock> start_time = std::chrono::steady_clock::now();
        for (const std::future<T>& future : *this)
        {
            future.wait_for(duration - (std::chrono::steady_clock::now() - start_time));
            if (duration < std::chrono::steady_clock::now() - start_time)
                return false;
        }
        return true;
    }

   
    template <typename C, typename D>
    bool wait_until(const std::chrono::time_point<C, D>& timeout_time) const
    {
        for (const std::future<T>& future : *this)
        {
            future.wait_until(timeout_time);
            if (timeout_time < std::chrono::steady_clock::now())
                return false;
        }
        return true;
    }
}; // class multi_future

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值