面试某些问题

线程池

线程池主要解决两个问题:线程创建与销毁的开销以及线程竞争造成的性能瓶颈。通过预先创建一组线程并复用它们,线程池有效地降低了线程创建和销毁的时间和资源消耗。同时,通过管理线程并发数量,线程池有助于减少线程之间的竞争,增加资源利用率,并提高程序运行的性能。
(1)线程创建开销解决
        多线程环境下,每当需要执行一个任务时,创建与销毁线程都需要额外的系统资源。线程池通过预先创建一定数量的线程,可以减少这种资源消耗。
(2)线程竞争问题解决
        过多的线程可能导致线程竞争,影响系统性能。线程池通过维护一个可控制的并发数量,有助于减轻线程之间的竞争。例如,当CPU密集型任务和I/O密集型任务共存时,可以通过调整线程池资源,实现更高效的负载平衡。

        线程池本质上是一种线程管理器,它可以管理一组线程,并且在有需要时创建新的线程。线程池通常包含三个组件:任务队列、工作线程和线程池管理器。其中,任务队列用于存放等待执行的任务,工作线程用于执行任务,线程池管理器用于管理线程池的状态和行为。

线程池的线程数量是一个非常重要的参数,它直接影响着线程池的性能和效果。线程池的线程数量通常由以下几个因素决定:
1. 任务类型
        
任务类型是线程池线程数量的一个关键因素。如果任务类型是CPU密集型任务,那么线程池的线程数量应该设置为CPU核心数的2倍左右。这是因为CPU密集型任务通常会占用大量的CPU资源,如果线程数量过多,反而会导致CPU资源的争夺和浪费。如果任务类型是I/O密集型任务,那么线程池的线程数量应该设置为CPU核心数的4倍左右。这是因为I/O密集型任务通常会涉及到大量的I/O操作,如果线程数量过少,会导致I/O操作的等待,从而浪费CPU资源。
 

线程池工作原理
        
线程池通过预先创建和调度复用线程来实现资源优化。这个过程主要包括:创建线程、任务队列与调度、以及线程执行及回收。
(1)创建线程
        线程池在初始化时会预先创建一定数量的线程,这些线程将会被后续任务复用。线程的数量可以根据实际需求和系统资源进行配置。

for (size_t i = 0; i < threadCount; ++i) {
    threads.emplace_back(threadFunc, this);
}

(2)任务队列与调度
        线程池通过维护一个任务队列来管理待执行任务。当线程池收到一个新任务时,它会将任务加入到任务队列中。线程会按照预定策略(例如FIFO)从队列中取出任务执行。以下是一个简单的任务队列操作示例:

void ThreadPool::addTask(const Task& task) {
    {
        lock_guard<mutex> lock(queueMutex);
        taskQueue.emplace(task);
    }
    condition.notify_one();
}

同时,线程池可能实现更复杂的调度策略,比如优先级调度、分组调度等。
(3)线程执行及回收
        线程执行任务时,会遵循线程池的调度策略从任务队列中获取任务。任务完成后,线程将被放回到线程池中等待下一个任务,而不是销毁。这种复用机制提高了资源利用率并降低了线程创建销毁的开销。以下是一个线程拿取任务及执行的例子:

void ThreadPool::threadFunc() {
    while (true) {
        Task task;
        {
            unique_lock<mutex> lock(queueMutex);
            condition.wait(lock, [this]() { return !taskQueue.empty() || terminate; });

            if (terminate && taskQueue.empty()) {
                break;
            }

            task = taskQueue.front();
            taskQueue.pop();
        }
        task(); // Execute the task.
    }
}

线程池的回收主要涉及任务完成通知、等待所有线程结束、资源回收与释放等方面,这部分内容将在后面的章节进行详细阐述。

多线程通信的方式

**同步:**互斥锁,条件变量,读写锁,信号量。
**异步:**信号

epoll缺点

深入理解EPOLLONESHOT事件

        即使使用ET模式,一个socket上的某个事件还是可能被触发多次,这是跟数据报的大小有关系,常见的情景就是一个线程,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒处理这些新的数据,于是出现了两个线程同时操作一个socket,为了避免这种情况,就可以采用epoll的EPOLLONESPOT事件。同时要注意,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket的EPOLLONESHOT的事件,以确保这个socket下次可读时,其EPOLLIN事件被触发,进而让其他的工作线程有机会继续处理这个socket。

生产者消费者模型

线程间同步通信最典型的例子就是生产者-消费者模型,生产者线程生产出产品以后,会通知消费者线程去消费产品;当消费者线程去消费产品时,发现还没有产品生产出来,它需要通知生产者线程赶快生产产品,等生产者线程生产出产品以后,消费者线程才能继续往下执行。

生产者把任务丢给线程池,线程池创建线程并处理任务,如果将要运行的任务数大于线程池的基本线程数就把任务扔到阻塞队列里,这种做法比只使用一个阻塞队列来实现生产者和消费者模式显然要高明很多,因为消费者能够处理直接就处理掉了,这样速度更快,而生产者先存,消费者再取这种方式显然慢一些。

C++的TLS

        多线程编程时,其实线程的访问权限是很高的,可以访问进程内存里的所有数据,甚至包括其他线程的堆栈,显然是需要制衡的。所以线程需要配备只有本线程专属的线程专属存储空间,即系统为线程单独开辟的存储空间。
使用方法:
2) 在C++11thread_local关键字或在C作为__thread关键字声明某类型的线程局部变量指针
3) 定义该线程局部变量的内存释放函数
4) 第一次使用该线程局部变量时,分配内存并调用pthread_atexit_add注册内存释放函数

        TLS被实现为每个线程对象内的数据数组。每个线程对象都有其自己的数组本地副本,并且每个数组的大小均相同。当您声明使用TLS的全局/静态变量时,它与这些数组的索引相关联(这是编译器/OS知道要分配多少个数组插槽的方式)。因此,在运行时访问变量时,实际上是在访问正在访问变量的线程上下文的数据数组中的关联插槽。

        虽然暂且并没有接触到太多的使用__declspec(thread)创建隐式TLS的场景,但是在多线程场景下,将多线程共享的资源组(比如对象池),则每个线程在每次操作时都会对对象池加锁申请一个对象然后解锁离开,这样子其实是人为地导致各处理线程在对象池外侧的等待阻塞,其实”完全可以借鉴运行库从系统中批发一块大内存零售给程序中各种需求“的思想,完全可以让各线程事先从全局的资源组总批发小批量资源,然后本线程专属使用,这样采用分而治之的策略可以减少对象池外waiting-to-do的阻塞情况。根据使用场景调整参数应该可以取得不错的加速效果。根据这样的想法搜到一篇类似的文章,可以参考https://www.cnblogs.com/sniperHW/p/3575816.html。

实现线程安全的方式

  1. 使用互斥量(mutex)来对共享资源进行保护。互斥量可以用来防止多个线程同时访问共享资源,从而避免数据竞争的问题。
  2. 使用读写锁(reader-writer lock)来对共享资源进行保护。读写锁允许多个读线程同时访问共享资源,但是写线程必须独占资源。这样可以在保证线程安全的同时,也尽可能地提高系统的并发性。
  3. 使用原子操作来对共享资源进行保护。在 C++ 中,可以使用 std::atomic 类型来定义原子变量,并使用原子操作来对共享资源进行操作。这样可以确保在多线程环境中,原子变量的操作是安全的。
  4. 使用条件变量(condition variable)来协调线程间的协作。条件变量可以用来在线程之间传递信号,从而控制线程的执行流程。
  5. 使用线程本地存储(thread-local storage)来保存线程的私有数据。线程本地存储可以用来给每个线程分配独立的存储空间,从而避免数据冲突的问题。

从键盘按下字符到它出现在显示器的过程

  1. 键盘被按下后,产生了硬件中断信号。其实是按下后发生了高低电平的切换,这就是信号。
  2. 计算机高级中断控制器(IOAPIC)选择CPU处理核心以及软件中断编号,并发送给中断描述符表(IDT)处理。
  3. 计算机根据IDT选择中断处理函数(系统调用)。
  4. 中断处理函数处理并通知端口驱动获取按键的信息。
  5. 端口驱动将数据封装,再把数据转交给负责显示的进程。
  6. 显示进程会把需要显示的内容输入到显卡中。
  7. 显卡再把处理好的图像数据写入一个自带的二维的缓冲区,在其上形成点阵数据。
  8. 最终点阵数据会投射到显示屏上显示。

协程

引入协程的目的:
在没有协程的时代,为了应对 IO 操作,主要有三种模型

(1)同步编程:应用程序等待IO结果(比如等待打开一个大的文件,或者等待远端服务器的响应),阻塞当前线程;

  • 优点:符合常规思维,易于理解,逻辑简单;
  • 缺点:成本高昂,效率太低,其他与IO无关的业务也要等待IO的响应;

(2)异步多线程/进程:将IO操作频繁的逻辑、或者单纯的IO操作独立到一/多个线程中,业务线程与IO线程间靠通信/全局变量来共享数据;

  • 优点:充分利用CPU资源,防止阻塞资源
  • 缺点:线程切换代价相对较高,异步逻辑代码复杂

(3)异步消息+回调函数:设计一个消息循环处理器,接收外部消息(包括系统通知和网络报文等),收到消息时调用注册的回调函数;

  • 优点:充分利用CPU资源,防止阻塞资源
  • 缺点:代码逻辑复杂

协程的概念,从一定程度来讲,可以说是“用同步的语义解决异步问题”,即业务逻辑看起来是同步的,但实际上并不阻塞当前线程(一般是靠事件循环处理来分发消息)。协程就是用来解决异步逻辑的编程复杂度问题的。

优点

  • 协程更加轻量,创建成本更小,降低了内存消耗
  • 协程有自己的调度器,减少了 CPU 上下文切换的开销,提高了 CPU 缓存命中率
  • 减少同步加锁,整体上提高了性能
  • 可以按照同步思维写异步代码,即用同步的逻辑,写由协程调度的回调

缺点

  • 在协程执行中不能有阻塞操作,否则整个线程被阻塞
  • 协程可以处理 IO 密集型程序的效率问题,但不适合处理 CPU 密集型问题

适用场景

  • 高性能计算,牺牲公平性换取吞吐。
  • 在 IO 密集型的热舞

HTTPS

同步和异步

同步和异步的概念描述的是应用程序与内核的交互方式,同步是指应用程序发起 IO 请求后需要等待或者轮询内核 IO 操作返回结果后才能继续执行;而异步是指应用程序发起 IO 请求后仍继续执行,当内核 IO 操作完成后会通知应用程序,或者调用应用程序的回调函数。同步和异步是相对于操作结果来说,会不会等待结果返回。

阻塞和非阻塞

阻塞和非阻塞的概念描述的是应用程序调用内核 IO 操作的方式,阻塞是指 IO 操作需要彻底完成后才返回到用户空间;而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,无需等到 IO 操作彻底完成。

互联网IT岗位分类

基础职位:

  • 后端开发(Java、C++、PHP、数据挖掘、搜索算法、精准推荐、C、C#、全栈工程师、.NET、Hadoop、Python、Delphi、VB、Perl、Ruby、Node.js、Go、ASP、Shell、区块链)、移动开发(HTML5、Android、iOS、WP、移动开发其它)
  • 前端开发(web前端、Flash、html5、JavaScript、U3D、COCOS2D-X、前端开发其它)
  • 人工智能(深度学习、机器学习、图像处理、图像识别、语音识别、机器视觉、算法工程师、自然语言处理)
  • 测试(测试工程师、自动化测试、功能测试、性能测试、测试开发、游戏测试、白盒测试、灰盒测试、黑盒测试、手机测试、硬件测试、测试经理、测试其它)
  • 运维(运维工程师、运维开发工程师、网络工程师、系统工程师、IT支持、IDC、CDN、F5、系统管理员、病毒分析、WEB安全、网络安全、系统安全、运维经理、运维其它)
  • 数据库管理(MySQL、SQLServer、Oracle、DB2、MongoDB、ETL、Hive、数据仓库、DBA其它)、项目管理(项目经理、项目助理)
  • 硬件开发(硬件、嵌入式、自动化、单片机、电路设计、驱动开发、系统集成、FPGA开发、DSP开发、ARM开发、PCB工艺、模具设计、热传导、材料工程师、精益工程师、射频工程师、硬件开发其它)

相关资料:

 线程局部存储TLS_墨篙和小奶猫的博客-CSDN博客

C语言程序从源文件到可执行文件的过程以及从键盘按下字符到它出现在显示器的过程_c语言按下键盘触发程序-CSDN博客https://blog.csdn.net/u013820121/article/details/128542514

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值