【从0开始编写webserver·基础篇#01】为什么需要线程池?写一个线程池吧-通讯

2023-05-14 20:27:57 来源: 博客园
线程池

参考:

1、游双Linux高性能服务器编程

2、TinyWebServer


(相关资料图)

注:虽然是"从0开始",但最好对(多)线程、线程同步等知识点有所了解再看,不然可能有些地方会理解不到位(但也有可能是我没说明到位,水平有限,见谅)

Web服务器与线程池的关系

Web服务器需要同时处理多个客户端请求,并且每个请求可能需要花费很长时间来处理,如数据库查询、文件读写等操作。

因此Web服务器不太可能是单线程的,要实现并发操作就必须引入多线程技术

使用线程池的服务器属于多线程服务器。线程池本质上是一种多线程技术,通过在程序启动时创建一定数量的工作线程,并将所有请求任务加入到任务队列中,以便线程池中的多个工作线程可以同时处理请求任务。

在Web服务器中,线程池的作用是有效地处理并发请求,提高服务器的并发能力和性能

具体地,Web服务器通过线程池实现以下几个方面的功能:

提高并发性能:线程池可以在同一时刻处理多个请求,从而提高服务器的并发处理能力,减少请求响应时间。控制线程数量:线程池可以控制同时执行的线程数量,从而避免线程数目过多导致系统资源耗尽。管理线程状态:线程池可以对工作线程进行管理,并监测工作线程的状态,如是否空闲、是否存活等。避免线程创建销毁开销:使用线程池可以避免频繁地创建和销毁线程,从而减少系统开销,提高服务器的性能。实现一个线程池

前置知识:互斥锁、信号量、C++模板编程

(注:代码中使用的)

线程池在代码实现上是一个模板类,使用模板的原因是方便进行复用

线程池模板类,主要用于管理多个线程并处理任务。其中包含以下主要成员变量和函数:

成员变量:线程数量(m_thread_number)、最大请求数(m_max_requests)、线程池数组(m_threads)、请求队列(m_workqueue)、互斥锁(m_queuelocker)、信号量(m_queuestat)和是否停止标志(m_stop)。成员函数:构造函数(threadpool())、析构函数(~threadpool())、添加任务(append())、子线程中要执行的工作函数(worker())和启动线程池中的一个或多个线程进行任务处理的函数(run())。

下面是该代码的工作流程和原理:

​1、创建线程池

创建时,会先判断线程数和最大请求数是否小于等于0。之后,会创建一个大小为 m_thread_number 的线程池数组,并将其设置为线程脱离状态,即在创建完线程之后就可以将其与当前的进程分离,避免线程阻塞主线程和其他相关线程的运行。如果创建时失败,则抛出异常。

​2、添加任务

往任务队列中添加请求时,会先加锁(使用互斥锁),以确保多个进程不会争抢。之后,会判断任务队列中的请求数是否超过 m_max_requests,如果队列大小大于最大请求数,则解锁并返回 false。否则,将请求添加到队列中,解锁并增加信号量,通知线程池中的线程,有新任务需要处理。最终返回 true。

​3、线程工作函数

当收到信号量后,线程会先上锁(使用互斥锁),以确保多个线程不会同时访问队列。之后,会判断任务队列是否为空。如果队列为空,则解锁并继续等待下一次信号量的到来。否则,取出队列顶部的请求,并将其弹出队列。最后,解锁,并调用任务函数(request->process())。

​4、析构函数

执行析构函数时,将 m_stop 设置为 true,供线程判断是否要停止。

定义线程池类

先来定义一下线程池类

template threadpool {private:    int m_thread_number;//线程数    int m_max_requests;//最大请求数    bool m_stop;//停止符    pthread_t* m_thread;//线程池数组    std::list m_workqueue;//任务队列    locker queuelocker;//互斥锁,locker是对系统提供的mutex方法的封装,位于locker.h中    sem m_queuestat;//信号量private:    static void* worker(void* arg);//工作函数    void run();//线程池的主函数,用于检查任务队列中的请求    public:    threadpool(int m_thread_number = 8, int m_max_requests = 1000);//构造    ~threadpool();//析构    bool append();//将请求加入m_workqueue中   }

其实也没有很复杂,但是有以下几个点需要注意:

1、线程池数组m_thread和任务队列m_workqueue是没有直接联系的

在线程池类初始化时,线程池数组就会根据指定的 线程数m_thread_number 来创建对应数量的线程

这些线程会被阻塞(一直调用 run函数 检查 m_workqueue 中有无请求),直到 **外部调用线程池对象的代码 **通过调用threadpool类的对象的append()方法 向任务队列中添加新的任务

这时,append()才会定义模板类中声明的 任务队列m_workqueue(也就是往队列中push请求任务)

此时,之前被创建的某个线程检查到队列中有任务,于是其获取到了锁

将队列中的请求弹出,调用对应的任务函数进行处理

综上,虽然线程池数组和任务队列没有直接联系,但是他们具有协作关系,实现服务器对于请求的监听和处理操作

2、工作函数为什么要定义为静态的,并且其返回值为什么是void*

在C语言中,函数的返回值只能是一个类型。但是有些时候,我们需要从一个函数中返回多个值或者不同类型的值

在这种情况下,我们可以使用指针或者void指针来达到这个目的。

在这里,worker被定义为static void*,其中void*表示一个指向内存地址的指针,但是它没有指定具体的数据类型。

这意味着worker函数可以返回任何类型的指针,这使得worker函数具有更大的灵活性,并且可以处理各种不同类型的数据

同时,通过将worker函数声明为static,它只能在当前文件中使用,可以避免与其他文件中的函数名称重复的问题。

好了,线程池模板类定义完了,现在需要在类外分别实现各个成员函数

按顺序来:构造函数->析构函数->...

实现线程池构造函数

构造函数负责根据给定的thread_number来创建线程

首先,我们需要判断所给的参数范围是否合法

然后创建线程池数组,大小为thread_number,最后调用pthread_create函数创建线程,使用pthread_detach函数将线程设置为脱离状态

template //通过参数列表进行初始化threadpool::threadpool(int thread_number, int max_requests):m_thread_number(thread_number),m_max_requests(max_requests),    m_stop(false), m_threads(NULL){    //异常判断,线程数和最大请求数小于0,报错        if((thread_number <= 0) || (max_requests <= 0)){            throw std:: exception();        }        m_threads = new pthread_t[m_thread_number];//创建线程池数组        if(!m_threads){            throw std:: exception();        }        for(int i = 0; i < thread_number; ++i){            printf("创建第 %d 个线程\n", i);            if(pthread_create(m_threads + i, NULL, worker, this) != 0){                delete[] m_threads;                throw std::exception();//创建失败            }            if(pthread_detach(m_threads[i])){//在调用pthread_detach()函数之后,线程将进入“分离”状态,这意味着它不能再被其他线程或主线程等待和加入。            }        }     } 

从构造函数可知,线程是在线程池创建时就被创建的,并且数量是固定的

有以下注意点:

1、pthread_create函数的传入参数

pthread_create()函数需要四个输入参数,分别是:

1.线程标识符指针(pthread_t *),用于存储新创建线程的标识符;

2.线程属性指针(const pthread_attr_t *),用于设置新线程的属性。如果不需要设置,则可以将该参数设置为NULL;

3.指向函数的指针(void (start_routine) (void *)),用于作为新线程的入口点。新线程开始执行时会从该函数开始执行;

4.传递给新线程入口点函数的参数指针(void *),该参数可以是任意类型的指针,它会被传递给新线程入口点函数。

在本代码中,pthread_create()函数的第一个参数是一个pthread_t类型的指针,该指针用于存储新创建线程的标识符。

第二个参数设置为NULL,因为我们不需要设置新线程的属性。

第三个参数是一个指向worker函数的指针,作为新线程的入口点。

最后一个参数是一个指向当前threadpool对象的指针,它被传递给worker函数作为参数,让worker函数能够访问到threadpool对象的所有

成员。

m_threads + i表示将 m_threads指针 向后偏移 i 个 pthread_t类型的长度,即指向线程池中第i个工作线程的标识符。

m_threads 是一个指向pthread_t类型的数组,当使用 m_threads[i] 时,实际上是对m_threads数组中第i个元素进行访问

因此,m_threads + i表示对 m_threads数组 进行偏移,使其指向第i个元素的地址。

pthread_create()函数中,需要传递一个指向线程标识符的指针作为参数,来保存新建线程的标识符。

因此,可以使用 m_threads + i作为该参数,表示将指向第i个工作线程的标识符的地址传递给pthread_create()函数。

worker是一个静态成员函数,它作为线程执行的入口点,用于处理任务队列中的请求

this指针是一个指向当前threadpool对象的指针,它被传递给worker函数作为参数。

由于worker函数是静态的,因此无法访问threadpool对象的非静态成员。

(为了防止重名所以设为静态)

因此,需要将threadpool对象的指针作为参数传递给worker函数,以便让worker函数能够访问threadpool对象的所有成员。

在pthread_create()函数中,需要将worker函数的指针作为参数传递,而this指针则用于向worker函数传递threadpool对象的指针。

通过这种方式,就可以让worker函数访问到threadpool对象的所有成员变量和成员函数。

析构函数

主要作用就是停止线程池。删除线程池数组,并回收资源

//实现析构函数  templatethreadpool::~threadpool(){    delete[] m_threads;//用完之后就把线程池数组删除    m_stop = true;//执行析构函数时将其置为true,供线程判断是否要停止}
实现工作函数worker

从上面的分析可知,worker函数 会在线程池初始化时被构造函数调用

templatevoid* threadpool::worker(void* arg){    threadpool* pool = (threadpool* )arg;    pool->run();    return pool;}

该函数主要做的事情就是就是接收一个线程池对象

具体来说, worker函数 从 pthread_create()函数 中得到了一个void类型的指针作为输入参数。

这个指针可以通过 pthread_create() 的最后一个参数(也就是 this )进行传递,即线程创建时调用的arg参数。

什么意思呢?

我们要调用pthread_create()函数创建一个线程,此时我们需要提供线程的存储位置线程属性参数新线程的入口函数以及该入口函数所需的参数的指针

在创建线程时,需要传入一个入口函数的指针,用于告诉操作系统新线程应该从哪里开始执行程序代码。在这里,我们将worker函数作为入口函数,以便启动一个新线程,并且让该线程执行线程池的工作函数run()。

也就是说,我们在实例化一个线程池类的时候(假设为A),同时也把一个指向A的指针作为参数传给了worker函数(通过pthread_create()函数),worker函数再调用A中的成员函数run()用于处理任务

由于run()函数被定义为非静态成员函数,无法直接作为入口函数使用,因此我们选择了worker函数作为入口函数,并在其中调用run()函数。在C++中,可以将任何函数作为新线程的入口函数,只要它符合线程函数的格式要求(即返回值为void*类型,参数为void*类型)。在实际编程中,通常会选择一个适当的函数作为入口函数,以实现所需的功能。

实现任务处理函数run

新线程以worker函数为入口进行执行后,会通过指针去调用run()函数,通过循环不断检查任务队列来获取任务(如果有的话)

取到任务之后会调用相应的处理函数进行处理(这里是process(),还没定义)

templatevoid threadpool::run(){    while(!m_stop){        //阻塞等待捕获sem信号量        m_queuestat.wait();                //拿到信号量之后上锁        m_queuelocker.lock();                if(m_workqueue.empty()){            m_queuelocker.unlock();//若队列为空就解锁            continue;        }        //取出队列头部的请求        T* request = m_workqueue.front();        m_workqueue.pop_front();        m_queuelocker.unlock();//解锁                if(!request) continue;//没有东西就继续循环        request->process();//有就调用对应的处理函数    }}

这里用到了互斥锁与信号量

上锁是为了保证在多线程的情况下,不会出现资源争夺的情况,保证线程安全

信号量的使用则涉及到了服务器整体的设计结构,后面再说(面试重点

实现添加任务函数append

到目前为止,一个所谓的"线程池"已经基本完工

这个"池"可以创建一个数组存放创建好的线程对象,并维护一个任务队列,从队列中不断检查是否有新任务(外界对服务器的请求)到来

从代码来看,run函数只有在收到信号量时才会去检查队列

那么是谁负责发送(改变)信号量呢?那肯定是负责将任务加入队列的那个部分,也就是append函数了

templatevoid threadpool::append(T* request){    //触发append就意味着有新请求来了,此时需要有线程来处理,所以为了安全要上锁    m_queuelocker.lock();    //判断当前队列中的任务是否已经达到最大请求上限    if(m_workqeue.size() > m_max_requests){        m_queuelocker.unlock();//是就解锁        return false;//添加失败    }    m_workqueue.push_back(request);//往任务队列添加一个请求    m_queuelocker.unlock();        m_queuestat.post();//修改信号量    return true;    }

在向队列添加任务后,append修改了信号量m_queuestat,使得阻塞在wait()处的run函数开始检查队列,获取刚被加入到队列中的任务

至此,线程池的全部功能实现完毕

完整代码threadpool.h
#ifndef THREADPOOL_H #define THREADPOOL_H#include #include #include #include "locker.h"//线程池类,将其定义为模板类是为了代码的复用//模板参数T就是任务类templateclass threadpool {private:    //线程数量    int m_thread_number;    //线程池数组,大小为m_thread_number    pthread_t * m_threads;//使用pthread_t一是为了性能,二是为了线程安全(相对于vector来说)    //请求队列中最多允许的待处理请求数    int m_max_requests;    //请求队列    std::list m_workqueue;    //互斥锁    locker m_queuelocker;    //信号量,用于判断是否有任务需要处理    sem m_queuestat;    //是否结束线程    bool m_stop;private:    //子线程中要执行的代码    static void* worker(void* arg);    void run();public:    threadpool(int thread_number = 8, int max_request = 10000);    ~threadpool();    bool append(T* request);};//模板外实现线程池构造函数template//参数列表初始化threadpool::threadpool(int thread_number, int max_requests):    m_thread_number(thread_number),m_max_requests(max_requests),    m_stop(false), m_threads(NULL){        //异常判断,线程数和最大请求数小于0,报错        if((thread_number <= 0) || (max_requests <= 0)){            throw std:: exception();        }        m_threads = new pthread_t[m_thread_number];//创建线程池数组        if(!m_threads){            throw std:: exception();        }        //创建thread_number个线程,并将它们设置为线程脱离        //线程脱离指的是在一个多线程程序中,某个线程完成了它原本需要执行的任务之后,        //并不立即结束自己的执行,而是继续保持运行状态,直到其他线程也完成了它们的任务之后才退出。        //这种情况下,该线程被称为“脱离线程”(detached thread)        /*线程脱离通常用于需要长时间运行的后台任务,通过将这些任务单独分配给脱离线程来处理,可以避免阻塞主线程和其他相关线程的运行。*/        for(int i = 0; i < thread_number; ++i){            printf("创建第 %d 个线程\n", i);            //C++里面的woker是静态的,所以要传入this来访问类里变量            /*在C++中,对指针进行加减操作会根据指针类型的大小进行调整。            因此,m_threads + i表示将m_threads指针向后偏移i个pthread_t类型的长度,即指向线程池中第i个工作线程的标识符。            m_threads是一个指向pthread_t类型的数组,当使用m_threads[i]时,实际上是对m_threads数组中第i个元素进行访问。            m_threads + i表示对m_threads数组进行偏移,使其指向第i个元素的地址。            在pthread_create()函数中,需要传递一个指向线程标识符的指针作为参数,来保存新建线程的标识符。            因此,可以使用m_threads + i作为该参数,表示将指向第i个工作线程的标识符的地址传递给pthread_create()函数。*/            if(pthread_create(m_threads + i, NULL, worker, this) != 0){//为了让worker访问非静态成员,传入this                delete[] m_threads;                throw std::exception();//创建失败            }            if(pthread_detach(m_threads[i])){//在调用pthread_detach()函数之后,线程将进入“分离”状态,这意味着它不能再被其他线程或主线程等待和加入。            }        }    }//实现析构函数  templatethreadpool::~threadpool(){    delete[] m_threads;//用完之后就把线程池数组删除    m_stop = true;//执行析构函数时将其置为true,供线程判断是否要停止}//实现appendtemplatebool threadpool::append(T* request){//往队列中添加任务,要保证线程同步    m_queuelocker.lock();//添加互斥锁    if(m_workqueue.size() > m_max_requests){//任务队列大小大于最大请求数        m_queuelocker.unlock();//解锁并报错,此时的任务数已经超出上限        return false;    }    m_workqueue.push_back(request);//往队列中增加一个请求    m_queuelocker.unlock();//解锁    //将请求加入工作队列的操作是需要保证其原子性的,因此需要互斥锁保证多个进程不会争抢    m_queuestat.post();//增加信号量,通知线程池中的线程,有新任务需要处理    return true;    /*当一个新的任务被添加到队列中时,会调用 m_queuestat.post() 增加信号量。    在线程池初始化时,每个工作线程都被创建并阻塞在 m_queuestat.wait() 上等待信号量的触发。    一旦 m_queuestat 的值大于 0,其中的一个线程就会从阻塞状态唤醒并开始处理队列中的请求。*/}template//线程池的工作函数,其中模板参数T未被使用。该函数是作为新线程启动时调用的入口函数void* threadpool::worker(void* arg){    // 传入void 类型指针 arg     /*arg 是在启动线程时传递给该线程函数的参数。    以下代码中,它被转换为 threadpool* 类型,因为它实际上是一个指向 threadpool 结构体的指针。    然后,将这个指针赋值给名为 pool 的变量,以便在该函数中访问和操作 threadpool 结构体的成员。*/    threadpool* pool = (threadpool* ) arg;//在pthread_create中传入worker    pool->run();//启动线程池中的一个或多个线程,并将待处理任务提交给线程池进行处理    return pool;}templatevoid threadpool::run(){    while(!m_stop){        m_queuestat.wait();//等待append函数传过来的信号量,收到表示需要运行线程池,使用其中的线程处理来处理任务        //可能有数据到了,上锁        /*关于为什么这里要上锁:            收到信号量时,任务队列 m_workqueue 可能为空,也可能不为空,这取决于在等待信号量之前是否有新任务被添加到了队列中。            如果没有新任务被添加,那么 m_workqueue 仍然为空。如果有新任务被添加,那么 m_workqueue 将不为空。            需要注意的是,在多线程编程中,一个线程在等待信号量时,另一个线程可能会往任务队列中添加新任务,因此需要通过加锁(比如互斥锁)来保证对任务队列的访问是线程安全的。            这样可以避免出现竞态条件(race condition,也就是线程不同步),从而确保程序的正确性。        */        m_queuelocker.lock();        /*在等待信号量的线程执行之前,如果没有任何其他线程向任务队列中添加新的任务,那么收到信号量时 m_workqueue 可能为空。这种情况可以出现在以下几种情况下:            在初始化程序时,创建了一个空的任务队列并等待信号量,此时 m_workqueue 为空。            所有的任务都已经被处理完毕,并且等待信号量的线程尚未收到新的任务添加进来。            等待信号量的线程刚刚完成了处理该任务队列中的所有任务,然后又立即等待信号量,此时 m_workqueue 为空。        */        if(m_workqueue.empty()){            m_queuelocker.unlock();//解锁            continue;//继续循环,查看队列中是否有数据        }        //取出队列顶部的请求,并将其弹出队列        T* request = m_workqueue.front();        m_workqueue.pop_front();        //取完请求后,解锁        m_queuelocker.unlock();        if(!request){            continue;//没获取到就继续循环        }        //调用任务函数        request->process();    }}#endif
locker.h
#ifndef LOCKER_H //没定义就定义一个LOCKER_H#define LOCKER_H#include //互斥锁相关#include #include //信号量相关//线程头部机制的封装类//互斥锁类class locker{private:    pthread_mutex_t m_mutex;//创建一个互斥锁public:    locker(){//构造函数        if(pthread_mutex_init(&m_mutex, NULL) != 0){//初始化一个互斥锁,默认属性            throw std::exception();//抛出异常        }    }    ~locker(){//析构函数,销毁        pthread_mutex_destroy(&m_mutex);    }        bool lock(){//上锁        return pthread_mutex_lock(&m_mutex) == 0;//判断当前线程是否成功获取到了互斥锁 m_mutex。如果返回值为0,则表示当前线程已经成功获取到了该互斥锁;如果返回值不为0,则表示当前线程未能获取到该互斥锁。    }    bool unlock(){        return pthread_mutex_unlock(&m_mutex) == 0;    }    pthread_mutex_t * get(){//获取互斥量        return &m_mutex;    }};//条件变量类//判断队列中有无数据,没有就让线程停着,有就唤醒线程class cond {private:    pthread_cond_t m_cond;//创建一个条件变量public:    cond(){//构造函数        if (pthread_cond_init(&m_cond, NULL) != 0) {            throw std::exception();        }    }    ~cond() {//析构函数        pthread_cond_destroy(&m_cond);    }        bool wait(pthread_mutex_t *m_mutex) {        int ret = 0;        ret = pthread_cond_wait(&m_cond, m_mutex);        return ret == 0;    }    bool timewait(pthread_mutex_t *m_mutex, struct timespec t) {//超时        int ret = 0;        ret = pthread_cond_timedwait(&m_cond, m_mutex, &t);        return ret == 0;    }    bool signal() {//唤醒一个或多个线程        return pthread_cond_signal(&m_cond) == 0;    }    bool broadcast() {//唤醒所有线程        return pthread_cond_broadcast(&m_cond) == 0;    }};//信号量类class sem{private:    sem_t m_sem;public:    sem(){        if(sem_init(&m_sem, 0, 0) != 0){            throw std:: exception();        }    }    ~sem(){        sem_destroy(&m_sem);    }    //等待信号量    bool wait(){        return sem_wait(&m_sem) == 0;    }    //增加信号量    bool post(){        return sem_post(&m_sem) == 0;    }};#endif

标签:

【从0开始编写webserver·基础篇#01】为什么需要线程池?写一个线程池吧-通讯

线程池参考:1、游双Linux高性能服务器编程2、TinyWebServer注:虽然是"从0开始",但最好对(多)线程、线

05-14 20:27:57

东方9万多亩南繁水稻制种迎来收割季节 农户家门口挣钱忙|环球新要闻

在东方市感城镇南繁水稻制种产业园田野上,饱满的稻谷将翠绿的稻秆压弯下了腰,大部分稻谷已经呈现出金黄色

05-14 19:10:13

淘我喜欢登录_淘我喜欢

1、应该是有区别的,不过区别也不大,淘我喜欢是一家淘宝指定的购物网在,信誉么,跟淘宝一样的,退货,换

05-14 18:03:26

世界热文:有享网怎么样 有享网

今天来聊聊关于有享网怎么样,有享网的文章,现在就为大家来简单介绍下有享网怎么样,有享网,希望对各位小

05-14 17:00:18

闪婚总裁契约妻_闪辉电池|世界通讯

2014年11月15日,闪惠(北京)科技有限公司更名为北京名品轩文化发展有限公司。BENEFLY-Flash系列产品源

05-14 15:59:08

世界今日讯!武汉戏迷,好“戏”已就位,就等你了!

附:演出日程表(来源:长江云)【来源:长江云】版权归原作者所有,向原创致敬

05-14 14:58:13

热点聚焦:sophie天使疗愈 sophie

今天来聊聊关于sophie天使疗愈,sophie的文章,现在就为大家来简单介绍下sophie天使疗愈,sophie,希望

05-14 13:15:40

【新视野】倩女幽魂手游师徒成语牍录成语大全(倩女幽魂手游师徒成语)

倩女幽魂手游师徒成语牍录成语大全,倩女幽魂手游师徒成语这个很多人还不知道,现在让我们一起来看看吧!1、

05-14 12:14:14

四川彭山:杂交水稻母本移栽忙

人民网成都5月14日电 近日,四川省眉山市彭山区制种基地田间地头一派繁忙,工人们正在进行杂交水稻母本秧

05-14 11:01:13

今亮点!2023年夏天会有多热(2023年夏天会有台风吗)

夏天是生活中比较炎热的一个季节,每年这个时候都会出现高温天气,部分地区可能会有四十多度的高温,根据最

05-14 10:06:37

崇凭铁路比武忙

原标题:崇凭铁路比武忙工人日报-中工网记者庞慧敏通讯员鲁颖卜来龙  5月10日至12日,由广西壮族自治区崇

05-14 09:13:58

工龄养老金怎么上涨?40年、35年、25年、15年领多少? 通讯

工龄40年、35年、25年、15年,养老金上涨额有多大差距吗?跟随社保网小编一起来预算一下。视同工龄认定是退

05-14 08:03:33

世界资讯:造梦西游4冥海怎么过_造梦西游4怎么抓宠物

1、造梦西游4宠物在神兽森林使用葫芦道具进行捕抓!注:如果捕抓到的是宝宝的话,升级到20级还没领悟到技能

05-14 07:00:02

环球聚焦:我发现互联网工作的性价比还在持续走低,没看到好转的迹象

互联网行业工作的性价比,越来越低,里面的工作也会变得越来越普通,这不仅是进行时,也是一个未来时。其中

05-14 05:57:36

具体行政环境的解释_什么是具体行政环境

1、行政环境就是政府管理的环境,它是指围绕行政活动和行政现象这一主体的外部境况,是各种直接地或间接地

05-14 03:07:04

蔚来ET7,电动机总功率480kW,纯电续航675km,售价51.6万 世界微速讯

欢迎来到本期汽车评测,今天要向大家介绍的是蔚来ET7(图片|配置|询价)2023款100kWh。这款汽车性能稳定,乘

05-13 23:44:08

天天报道:厉害了!惠州63家企业上榜省重点农业龙头企业

惠州日报讯(记者刘建威)广东省农业农村厅日前发布《关于公布2022年广东省重点农业龙头企业名单的通知》,

05-13 21:55:51

【天天播资讯】陶行知简介视频_陶行知简介

1、陶行知(1891~1946年)是中国历史上伟大的人民教育家。2、1891年10月18日生于安徽歙县。3、他自幼聪明好

05-13 20:59:54

中国星辰|天舟“专送” 使命必达_世界报资讯

点击看视频央视网消息(焦点访谈):中国航天又传来好消息。5月10日21时22分,长征七号遥七火箭搭载天舟六

05-13 18:53:29

长江航道维护水深全线大幅增加

新华社武汉5月12日电(记者李思远)据长江航道局消息,随着长江流域降水增加,长江干线水利枢纽调度流量骤

05-13 18:05:42

屁股眼出血怎么办用什么药_屁股眼出血怎么办

1、你好大便出血这可能是痔疮或肛裂动因素导致的多因上火便秘等因素诱发 在治疗上可使用槐角丸或三七化痔丸

05-13 17:02:56

世界热文:免费!半价!武汉多个景区宣布五月优惠政策

免费!半价!武汉多个景区宣布五月优惠政策,武汉景区五月最新优惠政策来了!其中,武汉欢乐谷、木兰天池、

05-13 16:03:37

去了上海才发现:满大街都在穿“夏裙”,时髦优雅,关键显气质 关注

你来上海多久了?或许你是土生土长的上海人,或许是和成千上万奋力拼搏的人一样,初来乍到。上海这个关于梦

05-13 14:56:49

每日速递:电脑主机配置推荐清单_电脑主机配置推荐

1、处理器G4560350主板b150自选4508g内存450GTX750ti750电源额定300W400机箱

05-13 13:56:39

《庆余年2》开机,斯人悄然离去 当前最新

《庆余年2》开机,《庆余年》项目的幕后功臣之一程武却不再出现在主创阵容里。就在官宣开机前一天,5月9日

05-13 12:43:10

小刺猬简笔画涂色可爱_小刺猬简笔画

1、首先我们画一条水平线。2、然后我们会画一个半圆,这里不是很远。让我们尽量把它画得圆一些。3、然后我

05-13 11:01:11

观天下!2023年哈尔滨学院成人高考(专升本、,高起本)招生专业

2023年哈尔滨学院成人高考(专升本、高起本)招生专业一、招生专业哈尔滨学院2023年(2023级)成人高等教育拟招

05-13 10:15:56

防止镍块再变石头,伦敦金属交易所要求对镍库存实施额外检查 每日速看

【防止镍块再变石头,伦敦金属交易所要求对镍库存实施额外检查】知情人士透露,在今年3月出现仓储镍被替换

05-13 09:40:21

亲子共读节目表演_容易表演的亲子小节目

1、亲子表演节目一般都是跳舞,给课堂打扫卫生,舞龙。2、像两组比赛投气球;两人三足跑步比赛;搭积木;做

05-13 07:51:31

丽水市域形象主题词 全球有奖征集正式启动

昨日,丽水市域形象主题词征集活动正式启动,诚挚邀请全球关心关注丽水的人士参加。为充分反映丽水市域形象

05-13 06:14:30

环球焦点!无障碍楼梯的作用_什么叫无障碍楼梯

1、无障碍楼梯可以做疏散楼梯,但是疏散楼梯不一定是无障碍楼梯,无障碍楼梯是在平常楼梯基础上附加了一些条

05-13 04:52:31

全球热文:孙悟空大闹蟠桃会的起因经过结果_孙悟空大闹蟠桃会

1、这篇神话故事是根据吴承恩的《西游记》中的“孙悟空大闹蟠桃会”的一部分改写的。2、故事分两部分写...

05-13 02:47:22

2023大湾区科学论坛“众里寻她”女科学家分论坛将于5月20日召开

南方网讯(记者 唐丽萍通讯员 粤妇宣)“众里寻她”女科学家分论坛将于5月20日在广州南沙召开。作为20...

05-13 00:03:32

临安有片杨梅大棚突然走红网络,但这样的风景在绍兴似乎很常见-环球速读

应该说是巧合,在前往绍兴之前,刚好在网上“刷”到临安有个塑料大棚突然走红的消息,未来感满满的“冰...

05-12 22:10:00

明月照家园 世界速讯

对年龄不同、身份各异的受众来说,“大靖明月”是共同的“家园”。受访者供图被裹挟在大数据缜密计算过...

05-12 21:40:14

环球消息!各地法治政府建设水平如何?这里有一份评估报告

“在2021-2022年对全国100个地方城市法治政府建设水平的评估中,法治政府建设水平呈现整体上稳步提升的态势

05-12 20:09:55

雀魂规则荣和_雀魂规则 世界热资讯

你们好,最近小未来发现有诸多的小伙伴们对于雀魂规则荣和,雀魂规则这个问题都颇为感兴趣的,今天小活为大

05-12 19:46:04

每日快播:让更多“新就业”轻装上阵

第九次全国职工队伍状况调查结果显示,全国新就业形态劳动者已占职工总数的21%。他们主要分布在交通出行、

05-12 19:09:49

每日关注!Mysteel日报:唐山型钢价格趋弱运行 成交偏强

一、市场总结  唐山型钢价格趋弱运行,早盘市场成交清淡,午后厂商心态稍有回暖,个别低价成交上量,整体

05-12 18:41:44

宏润建设:公司2022年度光伏发电业务实现净利润3381万元 天天精选

宏润建设(002062)05月12日在投资者互动平台表示:您好,根据已披露的公司2022年度报告,公司2022年度光伏

05-12 18:09:06

网约车4月单量同比增长48.3%,部分城市已发布饱和预警

5月11日,网约车监管信息交互系统发布了网约车4月运营数据。网约车监管信息交互系统4月共收到订单信息7 06

05-12 17:35:58

2024年龙宝宝命好不好 生肖命数分析 全球通讯

龙是中国传统文化中的神兽,因其身形威猛、充满活力、气势磅礴,一直以来都被认为是吉祥的象征。属龙的宝宝

05-12 17:26:42

热门看点:督办成功!“泥水路”升级“水泥路”,居民出行告别“跋山涉水”

4月24日,正观新闻“郑在办”服务平台报道了丽水路迟迟未修通,导致金水区锦艺金水湾观澜苑小区一千多户...

05-12 17:04:22

【天天聚看点】河南省农业综合开发有限公司董事长郑献锋接受纪律审查和监察调查

河南省农业综合开发有限公司董事长郑献锋接受纪律审查和监察调查2023年05月12日15:57中国网财经

05-12 16:40:50

关于街舞的电影简介介绍英语_关于街舞的电影简介介绍

对于关于街舞的电影这个问题感兴趣的朋友应该很多,这个也是目前大家比较关注的问题,那么下面小好小编就收

05-12 16:04:34

淘气天尊:沪指四连阴,下周再跌就抄底!

周五市场呈现震荡走弱的格局,投资者可以看到,早盘沪指低开5点于3304点,创业板高开不足1点于2277点,市场

05-12 15:51:04

当前资讯!韩国泡菜怎么腌制而成的_韩国泡菜怎么腌制

你们好,最近小品发现有诸多的小伙伴们对于韩国泡菜怎么腌制而成的,韩国泡菜怎么腌制这个问题都颇为感兴趣

05-11 19:30:08

5月22日截止!您的回答很重要!|焦点热议

尊敬的市民朋友:您好!根据《全国爱卫办关于开展倡导文明健康绿色环保生活方式活动评估工作的通知》统一要

05-11 18:11:40

中煤能源:目前井工三矿生产情况正常_天天快报

每经AI快讯,有投资者在投资者互动平台提问:井工三矿现在生产情况如何?中煤能源(601898 SH)5月11日在投

05-11 17:37:06

焦作税务:用心服务特殊人群

河南经济报记者史新旗通讯员刘亦辉日前,为切实解决老年人、残疾人等特殊人群在纳税缴费中的堵点难点痛点,

05-11 17:20:15

东方9万多亩南繁水稻制种迎来收割季节 农户家门口挣钱忙|环球新要闻
淘我喜欢登录_淘我喜欢
世界热文:有享网怎么样 有享网
闪婚总裁契约妻_闪辉电池|世界通讯
世界今日讯!武汉戏迷,好“戏”已就位,就等你了!
热点聚焦:sophie天使疗愈 sophie
【新视野】倩女幽魂手游师徒成语牍录成语大全(倩女幽魂手游师徒成语)
四川彭山:杂交水稻母本移栽忙
今亮点!2023年夏天会有多热(2023年夏天会有台风吗)
崇凭铁路比武忙
工龄养老金怎么上涨?40年、35年、25年、15年领多少? 通讯
世界资讯:造梦西游4冥海怎么过_造梦西游4怎么抓宠物
环球聚焦:我发现互联网工作的性价比还在持续走低,没看到好转的迹象
具体行政环境的解释_什么是具体行政环境
蔚来ET7,电动机总功率480kW,纯电续航675km,售价51.6万 世界微速讯
天天报道:厉害了!惠州63家企业上榜省重点农业龙头企业
【天天播资讯】陶行知简介视频_陶行知简介
中国星辰|天舟“专送” 使命必达_世界报资讯
长江航道维护水深全线大幅增加
屁股眼出血怎么办用什么药_屁股眼出血怎么办
世界热文:免费!半价!武汉多个景区宣布五月优惠政策
去了上海才发现:满大街都在穿“夏裙”,时髦优雅,关键显气质 关注
每日速递:电脑主机配置推荐清单_电脑主机配置推荐
《庆余年2》开机,斯人悄然离去 当前最新
小刺猬简笔画涂色可爱_小刺猬简笔画
观天下!2023年哈尔滨学院成人高考(专升本、,高起本)招生专业
防止镍块再变石头,伦敦金属交易所要求对镍库存实施额外检查 每日速看
亲子共读节目表演_容易表演的亲子小节目
丽水市域形象主题词 全球有奖征集正式启动
环球焦点!无障碍楼梯的作用_什么叫无障碍楼梯
全球热文:孙悟空大闹蟠桃会的起因经过结果_孙悟空大闹蟠桃会
2023大湾区科学论坛“众里寻她”女科学家分论坛将于5月20日召开
临安有片杨梅大棚突然走红网络,但这样的风景在绍兴似乎很常见-环球速读
明月照家园 世界速讯
环球消息!各地法治政府建设水平如何?这里有一份评估报告
雀魂规则荣和_雀魂规则 世界热资讯
每日快播:让更多“新就业”轻装上阵
每日关注!Mysteel日报:唐山型钢价格趋弱运行 成交偏强
宏润建设:公司2022年度光伏发电业务实现净利润3381万元 天天精选
网约车4月单量同比增长48.3%,部分城市已发布饱和预警
2024年龙宝宝命好不好 生肖命数分析 全球通讯
热门看点:督办成功!“泥水路”升级“水泥路”,居民出行告别“跋山涉水”
【天天聚看点】河南省农业综合开发有限公司董事长郑献锋接受纪律审查和监察调查
关于街舞的电影简介介绍英语_关于街舞的电影简介介绍
淘气天尊:沪指四连阴,下周再跌就抄底!
当前资讯!韩国泡菜怎么腌制而成的_韩国泡菜怎么腌制
5月22日截止!您的回答很重要!|焦点热议
中煤能源:目前井工三矿生产情况正常_天天快报
焦作税务:用心服务特殊人群
“太空快递”再出征 “硬核科技”齐上阵
X 广告
资讯
X 广告

Copyright ©  2015-2022 人人畜牧网版权所有  备案号:粤ICP备18023326号-36   联系邮箱:8557298@qq.com