【Linux】优秀的系统部分——线程池的基本设计思路

news/2025/9/26 15:11:09/文章来源:https://www.cnblogs.com/wzzkaifa/p/19111006

30.线程池

线程池的基本概念与设计原理

  • 线程池是一种线程使用模式,通过预先创建多个线程来提高效率。每次任务到来时创建线程会产生开销,影响整体效率。线程池内部维护多个线程,无任务时大部分线程休眠,有任务时唤醒指定线程处理。
  • 线程池的线程数目有上限,可以控制系统负载时的线程总数波动,保证系统稳定性。线程池适用于多种场景,如浏览器或手机app访问网站时的外部响应、批量化计算、数据存储、数据库访问等。虽然线程池不是编写服务器的最主要方式,但学习价值较高。
  • 线程池种类包括固定数量线程池和浮动线程池,固定数量线程池从任务队列获取任务执行,浮动线程池根据任务量动态调整线程数量。这里采用固定线程池设计,内部预先创建一批线程,提供任务队列,用户从外部提交任务,有任务时唤醒线程,无任务时线程休眠。

自主设计线程池

#pragma once
#include <iostream>#include <unistd.h>#include <string>#include <vector>#include <queue>#include <memory>#include "Log.hpp"#include "Mutex.hpp"#include "Cond.hpp"#include "Thread.hpp"namespace My_ThreadPool{using namespace My_Mutex;using namespace My_Log;using namespace My_Cond;using namespace My_Thread;using thread_t = std::shared_ptr<Thread>;const static int defaultnum = 5;void DefaultTest()//前期测试代码{while (true){LOG(LogLevel::DEBUG) <<"Test......";sleep(1);}}template <class T>class ThreadPool{private:bool IsEmpty() {return _ptasks.empty();}void HanderTask(std::string name){T t;LOG(LogLevel::INFO) <<"线程: " << name <<"执行HanderTask方法";while (true){{LockGuard lockguard(_lock);while (IsEmpty() && _isrunning){_wait_num++;_cond.Wait(_lock);_wait_num--;}if (IsEmpty() &&!_isrunning)break;t = _ptasks.front();_ptasks.pop();}t(name);}LOG(LogLevel::INFO) <<"线程: " << name <<"退出HanderTask方法";}public:ThreadPool(int num = defaultnum) : _num(num), _isrunning(false){for (int i = 0; i < _num; i++){_threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HanderTask, this, std::placeholders::_1)));LOG(LogLevel::INFO) <<"创建线程: " << _threads.back()->Name() <<"...Success";}}void Equeue(T t){LockGuard lockguard(_lock);// 加锁if (!_isrunning)return;_ptasks.push(t);if (_wait_num)_cond.Weak();}void Start(){if (_isrunning)return;_isrunning = true;for (auto &t : _threads){t->Start();LOG(LogLevel::INFO) <<"启动线程: " << t->Name() <<"...Success";}}void Wait(){for (auto &t : _threads){t->Jion();LOG(LogLevel::INFO) <<"线程: " << t->Name() <<"回收";}}void Stop(){if (_isrunning){_isrunning = false;if (_wait_num)_cond.WeakAll();// 如果有线程在可变参数下等待,唤醒所有线程,把没有执行完成的任务(如果有)去执行}}~ThreadPool() {}private:std::vector<thread_t> _threads;// 这里储存的是一个一个的指向Thread的智能指针int _num;// 线程个数std::queue<T> _ptasks;// 任务的指针队列Mutex _lock;Cond _cond;int _wait_num;bool _isrunning;// 线程池目前的工作状态};}
  • 线程池需要一个任务队列(task queue)来存储待执行的任务,任务队列可以存储各种类型的任务,包括对象和函数。由于需要支持不同函数类型和类类型,所以我们把线程池定义为一个类模版,方便后续使用时修改任务类型
线程池包含对象
  • std::vector<thread_t> _threads:线程池的目的是用来管理一系列的线程,因此线程池需要一个容器(如vector)来管理所有的线程实体,可以使用智能指针(如shared_ptr)来管理线程对象,vector用来储存指向Thread的智能指针。线程池在启动时需要创建指定数量的线程对象,并将这些对象存储在容器中。线程对象的创建可以通过循环和push_back操作完成,使用make_shared来创建线程对象并将其添加到容器中。(具体看构造函数的方法)
  • _num:线程个数,用于对所有线程的遍历操作比如线程等待,线程初始化
  • std::queue<T> _ptasks;:线程池需要一个任务队列(task queue)来存储待执行的任务,任务队列可以存储各种类型的任务,包括对象和函数
  • Mutex _lock;:对线程池的成员进行判断、++/–等操作都不是原子性的操作,多线程并行执行的时候可能会出现数据不一致的问题,因此在这些情况下需要加锁处理(保证线程互斥)
  • Cond _cond;:保证线程同步,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题
  • _wait_num:线程池运行时,某一时刻在条件变量下等待的线程数,用于判断是否需要执行唤醒操作
  • _isrunning:线程池目前的工作状态,修改这个状态可以让线程池停止
线程池的接口设计

线程池需要提供基本的接口,包括Start方法用于启动线程池,Stop方法用于停止线程池,Wait方法用于回收线程池,以及Equeue方法用于将任务加入队列。当然还有必不可少的构造和析构。

可以把这个复杂的表达式理解为:

// 创建一个函数,这个函数会调用当前对象的HanderTask方法。这个代码片段定义了一个lambda表达式,并将其赋值给一个名为auto thread_func的变量。这个lambda表达式的功能是调用类成员函数HanderTask,并将传入的字符串参数name传递给该函数。
auto thread_func = [this](std::string name)
{
this->
HanderTask(name);
};
// 用这个函数创建线程,并保存到线程列表中
_threads.push_back(std::make_shared<Thread>(thread_func));
  • 析构函数:在线程池中我们没有用到动态开辟内存空间,所以析构函数没有必须要执行的代码

    ~ThreadPool() {
    }
  • Start方法用于启动线程池:调用封装好的线程类的Start方法创建线程并打印日志,注意需要提前判断当前线程池的状态_isrunning,因为在构造函数中我们把线程池的初始状态设置为false,只有在这种状态下才需要Start

    void Start()
    {
    if (_isrunning)
    return;
    _isrunning = true;
    for (auto &t : _threads)
    {
    t->
    Start();
    LOG(LogLevel::INFO) <<
    "启动线程: " << t->Name() <<"...Success";}}
  • Equeue方法用于将任务加入队列:当线程池处于运行状态,我们可以调用Equeue函数将需要执行的任务(函数)传入线程池,这个函数在把任务入队列之后需要唤醒正在_cond条件变量下阻塞的线程

    void Equeue(T t)
    {
    LockGuard lockguard(_lock);
    // 加锁
    if (!_isrunning)
    return;
    _ptasks.push(t);
    if (_wait_num)
    _cond.Weak();
    }
  • Stop方法用于停止线程池:

void Stop()
{
if (_isrunning)
{
_isrunning = false;
if (_wait_num)
_cond.WeakAll();
// 如果有线程在可变参数下等待,唤醒所有线程,把没有执行完成的任务(如果有)去执行
}
}
线程调用方法
void HanderTask(std::string name)
{
T t;
LOG(LogLevel::INFO) <<
"线程: " << name <<
"执行HanderTask方法";
while (true)
{
{
LockGuard lockguard(_lock);
while (IsEmpty() && _isrunning)
{
_wait_num++;
_cond.Wait(_lock);
_wait_num--;
}
if (IsEmpty() &&
!_isrunning)
break;
t = _ptasks.front();
_ptasks.pop();
}
t(name);
}
LOG(LogLevel::INFO) <<
"线程: " << name <<
"退出HanderTask方法";
}
  1. T t;: 声明一个类型为T的对象t。T是线程池模板参数,代表任务类型。这个对象将被用来存储从任务队列中取出的任务。

  2. LOG(LogLevel::INFO) << "线程: " << name << "执行HanderTask方法";: 打印日志,表示线程开始执行HanderTask方法。

  3. while (true): 无限循环,直到线程池停止并且任务队列为空时跳出。

  4. 进入循环体,首先是一个代码块,用于限制锁的作用范围:

    {
    LockGuard lockguard(_lock);
    // 获取互斥锁,保证对任务队列的访问是互斥的。 
    while (IsEmpty() && _isrunning) // 如果任务队列为空并且线程池正在运行,则进入等待。
    {
    _wait_num++;
    // 增加等待线程计数
    _cond.Wait(_lock);
    // 等待条件变量,释放锁并阻塞,直到被唤醒。被唤醒后会重新获取锁。
    _wait_num--;
    // 被唤醒后,减少等待线程计数
    }
    if (IsEmpty() &&
    !_isrunning) // 如果任务队列为空且线程池已停止,则退出循环。
    break;
    t = _ptasks.front();
    // 从任务队列中取出一个任务
    _ptasks.pop();
    // 将任务从队列中移除
    } // 锁的作用域结束,自动释放锁。
  5. 执行任务:t(name);。注意,这个操作是在锁外执行的,这样多个任务可以并发执行,而不会互相阻塞。

  6. 循环结束后,打印日志表示线程退出HanderTask方法。

注意:在条件变量等待时,使用while循环检查条件是为了防止虚假唤醒。即使被虚假唤醒,如果条件不满足(队列仍为空且线程池在运行),线程会继续等待。

另外,在等待条件变量时,先增加了_wait_num,等待后减少,这是为了在停止线程池时能够知道有多少线程正在等待,以便唤醒它们。

在取出任务后,锁就被释放了,这样其他线程就可以同时从队列中取任务(如果队列中有多个任务)。而任务执行是在锁外进行的,这允许并发执行任务。

最后,当线程池停止时,会唤醒所有等待的线程,它们会检查条件,发现线程池已停止且队列为空,就会退出循环。

测试用例

#include "ThreadPool.hpp"
#include "Task.hpp"
int main()
{
ENABLE_CONSOLE_LOG();
std::unique_ptr<My_ThreadPool::ThreadPool<task_t>> tp = std::make_unique<My_ThreadPool::ThreadPool<task_t>>();tp->Start();for(int i = 0; i <10; i++){tp->Equeue(Push);sleep(1);}tp->Stop();tp->Wait();return 0;}
user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson33/ThreadPool$ ./threadpool
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [74] - 创建线程: thread-1...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [74] - 创建线程: thread-2...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [74] - 创建线程: thread-3...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [74] - 创建线程: thread-4...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [74] - 创建线程: thread-5...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [94] - 启动线程: thread-1...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [94] - 启动线程: thread-2...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [44] - 线程: thread-1执行HanderTask方法
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [94] - 启动线程: thread-3...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [94] - 启动线程: thread-4...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [94] - 启动线程: thread-5...Success
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [44] - 线程: thread-4执行HanderTask方法
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [44] - 线程: thread-2执行HanderTask方法
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [44] - 线程: thread-3执行HanderTask方法
[2025-09-22 20:21:50] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-3]
[2025-09-22 20:21:50] [INFO] [103745] [ThreadPool.hpp] [44] - 线程: thread-5执行HanderTask方法
[2025-09-22 20:21:51] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-4]
[2025-09-22 20:21:52] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-2]
[2025-09-22 20:21:53] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-3]
[2025-09-22 20:21:54] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-5]
[2025-09-22 20:21:55] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-1]
[2025-09-22 20:21:56] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-4]
[2025-09-22 20:21:57] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-2]
[2025-09-22 20:21:58] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-3]
[2025-09-22 20:21:59] [DEBUG] [103745] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-5]
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [65] - 线程: thread-4退出HanderTask方法
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [65] - 线程: thread-2退出HanderTask方法
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [65] - 线程: thread-1退出HanderTask方法
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [102] - 线程: thread-1回收
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [102] - 线程: thread-2回收
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [65] - 线程: thread-3退出HanderTask方法
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [65] - 线程: thread-5退出HanderTask方法
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [102] - 线程: thread-3回收
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [102] - 线程: thread-4回收
[2025-09-22 20:22:00] [INFO] [103745] [ThreadPool.hpp] [102] - 线程: thread-5回收

其他自定义类型

//Mutex.hpp
#pragma once
#include <iostream>#include <pthread.h>namespace My_Mutex{class Mutex{public:Mutex(const Mutex &) = delete;const Mutex &operator=(const Mutex &) = delete;Mutex(){int n = pthread_mutex_init(&_lock, nullptr);if (n != 0){std::cerr <<"err: pthread_mutex_init" << std::endl;exit(1);}}int Lock(){return pthread_mutex_lock(&_lock);}int Unlock(){return pthread_mutex_unlock(&_lock);}pthread_mutex_t* MutexPtr(){return &_lock;}~Mutex(){int n = pthread_mutex_destroy(&_lock);if (n != 0){std::cerr <<"err: pthread_mutex_destroy" << std::endl;exit(2);}}private:pthread_mutex_t _lock;};class LockGuard{public:LockGuard(Mutex &lock):_lock(lock){_lock.Lock();}~LockGuard(){_lock.Unlock();}private:Mutex &_lock;};}
//Cond.hpp
#pragma once
#include <iostream>#include <pthread.h>#include "Mutex.hpp"namespace My_Cond{class Cond{public:Cond(){pthread_cond_init(&_cond, nullptr);}void Wait(My_Mutex::Mutex &mutex){pthread_cond_wait(&_cond, mutex.MutexPtr());}void Weak(){pthread_cond_signal(&_cond);}void WeakAll(){pthread_cond_broadcast(&_cond);}~Cond(){pthread_cond_destroy(&_cond);}private:pthread_cond_t _cond;};} // namespace My_Cond
//Thread.hpp
#pragma once
#include <iostream>#include <string>#include <functional>#include <unistd.h>namespace My_Thread{using func_t = std::function<void(std::string name)>;static int num = 1;// 线程个数计数enum class TSTATUS{NEW,RUNNING,STOP};class Thread{private:static void *routine(void *args){Thread *t = static_cast<Thread *>(args);t->_status = TSTATUS::RUNNING;t->_func(t->Name());return nullptr;}public:Thread(func_t func) : _func(func){_name = "thread-" + std::to_string(num++);// 默认命名_status = TSTATUS::NEW;_joinable = true;_pid = getpid();}bool Start(){if (_status == TSTATUS::RUNNING)return true;int n = ::pthread_create(&_tid, nullptr, routine, this);if (n != 0)return false;return true;}bool Jion(){if (_joinable){int n = ::pthread_join(_tid, nullptr);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Stop(){if (_status == TSTATUS::RUNNING){int n = ::pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP;return true;}return false;}bool Detach(){_joinable = false;int n = ::pthread_detach(_tid);if (n != 0)return false;return true;}bool IsJoinable(){return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;pid_t _pid;bool _joinable;func_t _func;TSTATUS _status;};}

日志类后面会有一篇博客讲解…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/916995.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实用指南:Pycharm中切换虚拟环境

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

深圳品牌内衣t台秀邢台视频优化效果

众所周知&#xff0c;i9-9900KF相当于i9-9900K去除核显的版本&#xff0c;其它参考保持一致&#xff0c;所以在性能上也是相同的。不过在近期&#xff0c;intel牌牙膏厂再一次发布了一款九代酷睿i9-9900KS特别版&#xff0c;从产品型号S后缀上来看&#xff0c;确实有点类似于NV…

MyEMS 深度解析:核心功能模块、数据流转逻辑与工业能源优化落地路径

在工业4.0与“双碳”目标的双重驱动下,能源管理已成为制造业企业提升竞争力、实现可持续发展的核心环节。MyEMS 作为一套成熟的开源能源管理系统,因其功能的完备性、架构的开放性和落地的高效性而备受关注。本文将从…

如何选择合适的服务器租用商? - 实践

如何选择合适的服务器租用商? - 实践2025-09-25 13:13 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !i…

kettle插件-国产数据库金仓插件,助力国产数据库腾飞

场景:国产数据库(金仓,达梦,瀚高,海量等)信创环境下最近发展的势头很猛,今天我们一起来学习下人大金仓(电科金仓)数据库,从金仓数据库中读取数据以及使用金仓数据库作为kettle的资源仓库,废话少数,开干。 …

制造业碳足迹追踪:开源能源管理系统如何助力企业实现“碳数据可视化”?

在全球碳中和浪潮与绿色供应链要求下,制造业正面临日益严格的碳监管与披露压力。然而,对于许多企业而言,“碳足迹”仍是一个停留在报告中的抽象概念。碳数据从哪里来?如何保证准确?又如何用于实际决策?成为横亘在…

网站建设甲方给乙方的需求方案wordpress 仿新浪微博

引言&#xff1a; 在数字时代&#xff0c;数据安全成为一项至关重要的挑战。DevicData-D-XXXXXXXX勒索病毒&#xff08;以下简称DevicData病毒&#xff09;是这场战斗中的新敌人&#xff0c;它能够以毁灭性的方式加密您的数据&#xff0c;迫使您在数据和时间之间做出艰难的选择…

淘宝联盟必须要网站备案织梦网站模板 虎嗅网

什么是实景三维&#xff1f; 实景三维&#xff08;3D Real Scene&#xff09;是一个专业术语&#xff0c;它指的是对一定范围内人类生产、生活和生态空间进行真实、立体、时序化反映和表达的数字虚拟空间。它是新型基础测绘的标准化产品&#xff0c;也是国家重要的新型基础设施…

iframe安全盲区:支付信息窃取攻击的新温床 - 教程

iframe安全盲区:支付信息窃取攻击的新温床 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

学校网站建设报价表企业网站 批量备案

一.委托的多播&#xff08;Multicasting of a Delegate&#xff09;&#xff1a; 委托对象&#xff0c;使用 "" 运算符进行合并&#xff0c;一个合并委托调用它所合并的两个委托。使用"-" 运算符从合并的委托中移除组件委托。 注&#xff1a;只有相同类型…

通付盾 建设网站公司网站测速

今天有个朋友测试 SQLite&#xff0c;然后得出的结论是&#xff1a;SQLite 效率太低&#xff0c;批量插入1000条记录&#xff0c;居然耗时 2 分钟&#xff01; usingSystem.Data;usingSystem.Data.Common;usingSystem.Data.SQLite;//创建数据库文件File.Delete("test1.db3…

综合网表中有assign怎么办

解决Design Compiler生成的网表里有assgin的问题综合后的网表有时候会出现assign语句,出现这种情况是工具会报VIO-4错误Verilog assign or tran statements are written out.应该保证PR用的网表里只有标准单元,所以需…

ios26版本回退到ios18 - 指南

ios26版本回退到ios18 - 指南2025-09-25 12:58 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important…

极限与导数

极限 极限的定义 \(\epsilon-\delta\) 定义法: 称 \(\lim_{x\to c}f(x)=k\),当 \(\forall \epsilon>0,\exists \delta>0\) 满足:\(\forall c-\delta <x<c+\delta(x\neq c), k-\epsilon <f(x)< k…

怀柔建设网站哪个网站做视频赚钱

目录 1.题目概述 2.题解 思路分析 具体实现 1.题目概述 验证尼科彻斯定理&#xff0c;即&#xff1a;任何一个整数m的立方都可以写成m个连续奇数之和。 例如&#xff1a; 1^31 2^335 3^37911 4^313151719 输入一个正整数m&#xff08;m≤100&#xff09;&#xff0c;将…

信阳网站设计如何快速找到公司网站

今天我们就来学习逐帧动画,废话少说直接上效果图如下: 帧动画的实现方式有两种&#xff1a; 一、在res/drawable文件夹下新建animation-list的XML实现帧动画 1、首先在res/drawable文件夹下添加img00-img24共25张图片 2、新建frame_anim.xml [html] view plaincopy <?xml v…

详细介绍:SMTPman,smtp服务器的使用全解析与指南!

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

挑战用R语言硬干一百万单细胞数据分析 - 指南

挑战用R语言硬干一百万单细胞数据分析 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &qu…

WPF ListBox VirtualizingPanel.CacheLengthUnit=Item VirtualizingPanel.CacheLength=5,5

Install-Package Microsoft.Extensions.DependencyInjection; Totally speaking, VirtualizationPanel.CacheLengthUnits value is item and its performance is better than pixel, the latter may lead to Unpredic…