做网站个体户执照,网站建设模板平台,华为云企业邮箱登录入口,wordpress 果蔬论坛Linux#xff1a;线程池 线程池概念封装线程基本结构构造函数相关接口线程类总代码 封装线程池基本结构构造与析构初始化启动与回收主线程放任务其他线程读取任务终止线程池测试线程池总代码 线程池概念
线程池是一种线程使用模式。线程过多会带来调度开销#xff0c;进而影… Linux线程池 线程池概念封装线程基本结构构造函数相关接口线程类总代码 封装线程池基本结构构造与析构初始化启动与回收主线程放任务其他线程读取任务终止线程池测试线程池总代码 线程池概念
线程池是一种线程使用模式。线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用还能防止过分调度。
线程池的应用场景
需要大量的线程来完成任务且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务使用线程池技术是非常合适的。因为单个任务小而任务数量巨大你可以想象一个热门网站的点击次数。 但对于长时间的任务线程池的优点就不明显了对性能要求苛刻的应用比如要求服务器迅速响应客户请求接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池情况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题但是短时间内产生大量线程可能使内存到达极限出现错误
接下来本博客就在Linux上实现一个线程池。 封装线程
线程池本质是把多个线程组织起来然后统一使用这些线程给它们派发任务每个线程拿到任务后各自执行。
基本结构
既然要将线程组织起来我们就要先用一个类来描述一个线程比如线程的TID线程的名字等等。
首先定义一个类Thread其包含以下成员
template typename T
using func_t std::functionvoid(T);template typename T
class Thread
{
public:
private:pthread_t _tid; // 线程TIDstd::string _threadName; // 线程名func_tT _func; // 线程执行的函数T _data; // 执行函数要传入的参数
};第一个成员_tid就是该线程的TID第二个参数_threadName就是线程的名字。
创建线程的目的是为了让线程去执行函数那么当然要有一个成员来记录这个线程执行什么函数。此处第三个成员_func就是被线程执行的函数其类型为func_tT
template typename T
using func_t std::functionvoid(T);也即是说func_tT类型是一个void (T)类型的函数返回值为空可以传入一个T类型的参数。而线程的最后一个成员_data就是被传入的参数。
当用户使用这个线程类时需要给出线程要执行的函数该函数的参数另外的还要线程的名字。 构造函数
弄清需求后我们就可以很好写出该线程的构造函数了
Thread(func_tT func, const T data, std::string threadName none): _func(func), _data(data), _threadName(threadName)
{}构造函数有三个参数第一个func用于初始化线程调用的函数第二个data用于初始化要给函数传入的参数第三个用于指定线程的名字默认值为none。 相关接口
那么我们的线程类又要提供哪些接口
目前为止我们还没有真正创建一个线程而是通过类成员保存了线程的相关信息那么我们就要通过这些线程的相关信息来创建线程了。
第一个问题便是函数pthread_create用于创建线程要指定一个void* (*)(void*)类型的函数指针但是初始化线程是用户传入的函数是void (T)类型这要咋办
很简单先调用一个void* (void*)类型的中间函数threadEntrypthread_create先传入该函数随后线程就会去执行threadEntry再在threadEntry内部调用用户指定的函数并传入数据_func(_data)。
我先写一个版本
template typename T
class Thread
{
public:void* threadEntry(void* args){_func(_data);return nullptr;}bool start(){int ret pthread_create(_tid, nullptr, threadEntry, nullptr);return ret 0;}
};在start函数中通过pthread_create创建了线程线程的TID交给类成员_tid随后线程去调用threadEntry在threadEntry内部调用_func(_data)即调用用户传入的函数。
这可行吗我尝试编译一下 编译报错了报错为invalid use of non-static member function简单来说就是错误的调用了非静态成员函数。
为什么呢不妨再仔细想想threadEntry的类型真的是void* (*)(void*)吗该函数处于类的内部属于非静态成员函数第一个参数为this指针因此我们要把这个函数用static修饰让其变为静态成员函数此时它的类型才是void* (*)(void*)。
static void* threadEntry(void* args)
{_func(_data);return nullptr;
}现在问题又来了由于没有this指针该函数是得不到_func和_data这两个成员的这该怎么办
别忘了pthread_create是可以给函数传参的我们只需要把this指针作为threadEntry的参数传入随后通过this指针访问_func和_data。
template typename T
class Thread
{
public:static void* threadEntry(void* args){Thread* self static_castThread*(args);self-_func(self-_data);return nullptr;}bool start(){int ret pthread_create(_tid, nullptr, threadEntry, this);return ret 0;}
};在pthread_create中第四个参数传入this那么函数threadEntry的第一个参数args就是this指针了通过 static_castThread*(args)将其转化为Thread*类型赋值给self变量。此时self-_func(self-_data);就可以调用函数了。
最后再支持一下datach线程分离join线程等待
void deatch()
{pthread_detach(_tid);
}void join()
{pthread_join(_tid, nullptr);
}现在我们封装好了一个线程类。
实验一下这个线程类是否有效
void test(int args)
{while(true){cout args endl;sleep(1);}
}int main()
{Threadint t(test, 2024, thread-1);t.start();t.join();return 0;
}在main函数中Threadint t(test, 5, thread-1)定义了一个线程对象执行的函数为test给test传入的参数为2024线程名为thread-1。
如果创建成功那么线程就会去执行test函数并且循环输出2024。
输出结果 输出正确说明我们的线程类没有问题。 线程类总代码
我把这个Thread类放进头文件thread.hpp中方便后续使用。
thread.hpp代码如下
#pragma once#include iostream
#include functional#include unistd.h
#include pthread.htemplate typename T
using func_t std::functionvoid(T);template typename T
class Thread
{
public:Thread(func_tT func, const T data, std::string threadName none): _func(func), _data(data), _threadName(threadName){}static void* threadEntry(void* args){Thread* self static_castThread*(args);self-_func(self-_data);return nullptr;}bool start(){int ret pthread_create(_tid, nullptr, threadEntry, this);return ret 0;}void deatch(){pthread_detach(_tid);}void join(){pthread_join(_tid, nullptr);}private:pthread_t _tid; // 线程TIDstd::string _threadName; // 线程名func_tT _func; // 线程执行的函数T _data; // 执行函数要传入的参数
};封装线程池
现在我们通过类Thread描述了一个线程那么就可以用线程池来组织这些线程了。
当前目录结构如下 内部有三个文件第一个文件是主程序main.cpp以及两个自己的头文件Thread.hpp是刚刚封装的线程类我们将在ThreadPool.hpp内部实现线程池。
基本结构
线程池的运行模式如下 线程池内部维护多个线程和一个任务队列主线程往任务队列中放任务线程池内部的线程则执行任务队列中的任务。 那么毫无疑问的就是线程池内部至少要有一个数组管理多个线程以及一个队列来放任务
线程池threadPool内部的成员如下
template typename T
class threadPool
{
private:int _threadNum; // 线程总数int _waitNum; // 正在等待任务的线程数目bool _isRunning; // 当前线程池是否运行std::vectorThreadstd::string _threads; // 用数组管理多个线程std::queueT _taskQueue; // 任务队列
};在threadPool中有两个成员数组_threads任务队列_task_queue。
我们先前封装的Thread中模板参数T用于给线程执行的函数指定参数类型。在此我固定其为string类型后续线程执行函数时该参数用于传入线程的名字。
另外的我还额外指定了三个成员
_threadNum标识当前线程池的线程总数_waitNum当前有几个线程在等待任务_isRunning用于终止线程池
这三个成员都是对线程池本身的描述。
但是我们目前忽略了一个问题也是多线程编程最重要的问题线程的互斥与同步。
我们的任务是主线程往队列放任务其它线程从队列拿任务。那么就要考虑以下几个问题
多个线程可以同时拿任务吗不能任务队列是临界资源线程与线程之间要互斥。可以主线程放任务时其他线程拿任务吗不能主线程与执行任务的线程也要互斥。 由于它们都在竞争任务队列这一个资源我们只要用一把互斥锁即可完成以上的所有互斥。主线程和执行任务的线程都去争夺一把锁争到锁的线程才可以访问任务队列。 接下来就是同步问题
毫无疑问的是只有任务队列里面有任务时线程才能去任务队列中拿任务。因此要主线程先放任务其他线程后拿任务这就要一个条件变量来维护。
因此我们还要两个成员
template typename T
class threadPool
{
private:int _threadNum; // 线程总数int _waitNum; // 正在等待任务的线程数目bool _isRunning; // 当前线程池是否运行std::vectorThreadstd::string _threads; // 用数组管理多个线程std::queueT _taskQueue; // 任务队列pthread_mutex_t _mutex; // 互斥锁维护任务队列pthread_cond_t _cond; // 条件变量保证主线程与其他线程之间的同步
};构造与析构
接下来先写线程池的构造函数和析构函数在构造函数内部要完成的自然就包括_mutex的初始化_cond的初始化。而析构函数的任务自然是销毁它们。
template typename T
class threadPool
{
public:threadPool(int threadNum 5): _threadNum(threadNum), _waitNum(0), _isRunning(false){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);}~threadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}
};用户创建线程池的时候只要输入一个数字表名该线程池内部要有几个线程即可。一开始_isRunning是false表示线程还没有开始运行。
构造函数只是创建了锁条件变量以及各个线程内部的基本信息而已此时数组_threads还没有任何元素。也就是说我们目前连线程对象都没创建出来。 初始化
我们在此用一个init函数来初始化线程创建出线程对象创建Thread对象时要传入三个参数
Thread(func_tT func, const T data, std::string threadName none)这是我们刚刚写的Thread构造函数第一个参数传线程要调用的函数第二个参数传func的第一个参数第三个参数传线程名。
在此我们让线程去执行一个叫做handlerTask的函数这个函数内部实现线程的到任务队列拿任务的过程。
而handlerTask的第一个参数也是线程的名字以便在handlerTask内部识别是哪一个线程执行了任务。
如下
template typename T
class threadPool
{
public:void handlerTask(std::string){//执行任务队列的任务}void init(){for (int i 1; i _threadNum; i){std::string name Thread- std::to_string(i);_threads.emplace_back(handlerTask, name, name);}_isRunning true;}
};init中一个for循环创建_threadNum个线程第i号线程的名字是Thread-i。
数组尾插时(handlerTask, name, name)三个参数分别是线程要执行的函数线程名线程名。我们在此传入了两个线程名但是作用不一样。一个是handlerTask的参数一个是Thread内部的成员。
所以线程构建完毕后_isRunning true表示线程开始运作了。
但是以上代码还是犯了一个相同的错误Thread的函数类型要求是void (T)我们限制了T string那就是void (string)。但是handlerTask是非静态成员函数所以要加static。
在此我用C中的包装器bind来实现
void init()
{auto func bind(threadPool::handlerTask, this, std::placeholders::_1);for (int i 1; i _threadNum; i){std::string name Thread - std::to_string(i);_threads.emplace_back(func, name, name);}_isRunning true;
}通过包装器我把handlerTask的第一个参数绑定为了this使得类型变为functionvoid(string)从而符合Thread的构造函数。可以理解为此时变量func就是函数handlerTask不过类型变为了functionvoid(string)原先的第一个参数固定为this指针。
这个handlerTask函数我们稍后实现。 启动与回收
到目前为止我们已经创建好了一批线程并且指定了指向handlerTask函数但是限制线程还没有被启动。当时我们封装线程类时给Therad一个start来启动线程。此处的线程池也要一个allStart来调用所有线程start。另外的也要一个allJoin来调用所有的join回收线程。
代码
template typename T
class threadPool
{
public:void allStart(){for (auto th : _threads)th.start();}void allJoin(){for (auto th : _threads)th.join();}
};主线程放任务
现在先写一个enQueue接口让主线程往任务队列中投放任务。
投放任务的要求是
访问队列要与其他线程互斥即对_mutex加锁添加任务后此时一个线程就可以去访问任务队列了也就是线程同步
代码
template typename T
class threadPool
{
public:void enQueue(const T task){pthread_mutex_lock(_mutex);if (_isRunning){_taskQueue.push(task);if (_waitNum 0)pthread_cond_signal(_cond);}pthread_mutex_unlock(_mutex);}
};函数的参数为const T task即我们的任务类型是T这个T最好是一个可调用对象后续其它线程从任务队列拿任务时就可以调用这个函数。
首先对_mutex加锁确保主线程投放任务时没有其他线程正在访问队列。随后通过push把这个任务放进队列中。如果waitNum 0说明当前有线程在等待任务通过pthread_cond_signal唤醒一个线程让他来执行任务。
一切完毕后释放自己的锁。 其他线程读取任务
现在就到了线程池最复杂的一部分那就是其他线程读取任务的过程。
线程要保持互斥从任务队列拿任务时要对_mutex加锁其它线程要与主线程同步当任务队列为空就去_cond下面等待
先写一个雏形
template typename T
class threadPool
{
public:void handlerTask(std::string name){while (true){pthread_mutex_lock(_mutex);while (_taskQueue.empty()){_waitNum;pthread_cond_wait(_cond, _mutex);_waitNum--;}T task _taskQueue.front();_taskQueue.pop();std::cout name get a task... std::endl;pthread_mutex_unlock(_mutex);task();}}
};访问队列前首先对_mutex加锁保证互斥。随后进行条件判断taskQueue是否有任务如果有任务就直接拿走任务然后执行。如果没有任务就去_cond下面等待。此时_waitNum表示等待的线程多了一个当从pthread_cond_wait等待结束后就要_waitNum--。
这个地方套了一个while循环而不是if语句这是因为哪怕当前线程被主线程唤醒了也有可能发生伪唤醒其实_taskQueue内部根本没有任务。所以还要进入下一次while判断确保访问_taskQueue时一定是有任务的。
当从while出来后此时任务队列一定有任务所以可以放心调用front和pop接口。拿到任务后赋值给task。这里要先解锁后调度task。因为调度task时已经不算访问临界资源了而调度函数的时间可能很长此时先把锁释放掉让其他线程拿任务而不是自己执行完任务后才让别的线程拿任务这样和单线程就没有区别了。
但是目前还有一个问题如果线程访问任务队列时线程池已经被终止了咋办
线程池的终止与否是通过成员_isRunning来判定的在执行任务时判断一下_isRunning的值
如果当前没终止正常运行如果当前终止了 如果任务队列还有任务把任务执行完如果任务队列没任务当前线程退出
那么我们的代码就变成下面这样
template typename T
class threadPool
{
public:while (true){pthread_mutex_lock(_mutex);while (_taskQueue.empty() _isRunning){_waitNum;pthread_cond_wait(_cond, _mutex);_waitNum--;}//线程池终止了并且队列中没有任务了 - 线程退出if (_taskQueue.empty() !_isRunning){pthread_mutex_unlock(_mutex);std::cout name quit... std::endl; break;//线程离开while循环同时线程退出}//走到这一步一定还有任务要执行不论线程池有没有终止都先把任务做完T task _taskQueue.front();_taskQueue.pop();std::cout name get a task... std::endl;pthread_mutex_unlock(_mutex);task();}
};以上代码大致分为三个区域第一个while判断_taskQueue.empty() _isRunning如果进条件变量等待那么必须是线程池还没终止并且当且队列为空。
如果线程池终止了那么此时要么去拿任务要么直接退出。如果队列不为空毫无疑问就去拿任务。
随后进入第二个判断语句if (_taskQueue.empty() !_isRunning)即判断刚刚的while循环是哪一种情况结束的。如果是线程池结束并且任务队列为空那么就终止这个线程。剩下的情况就是任务队列有任务此时不论线程有没有退出都要把任务拿走执行掉。 终止线程池
终止线程池也不仅仅是直接_isRunning false这么简单要考虑一下问题 如果在stop时有线程正在调用handlerTask函数怎么办 此时多个线程访问变量_isRunning就有可能会造成线程安全问题所以访问_isRunning时也要加锁由于之前所有的访问_siRuiing的操作都在_mutex锁中所以和之前共用一把锁即可。 如果stop后还有线程在_cond下面等待怎么办 如果线程一直在_cond下面等待就会导致无法退出此时在_isRunning false之后还要通过pthread_cond_broadcast唤醒所有等待的线程让它们重新执行handlerTask的逻辑从而正常退出。
代码
template typename T
class threadPool
{
public:void stop(){pthread_mutex_lock(_mutex);_isRunning false; //终止线程池pthread_cond_broadcast(_cond); //唤醒所有等待的线程pthread_mutex_unlock(_mutex);}
};测试
现在我们已经有一个比较完整的线程池代码了我们用以下代码测试一下
int test()
{int a rand() % 100 1;int b rand() % 100 1;std::cout a b a b std::endl;return a b;
}int main()
{srand(static_castunsigned int(time(nullptr)));threadPoolint(*)(void) tp(3);tp.init();tp.allStart();for (int i 0; i 10; i){tp.enQueue(test);sleep(1);}tp.stop();tp.allJoin();return 0;
}通过threadPoolint(*)(void) tp(3);创建有三个线程的线程池执行的任务类型为int(void)但是要注意此处要传入可调用对象C的可调用对象有函数指针仿函数lambda表达式。此处我用了函数指针int(*)(void)。
接着init初始化线程池此时线程对象Thread已经创建出来了但是还有没创建线程。随后调用allStart此时才真正创建了线程。
然后进入一个for循环给任务队列派发任务总共派发十个任务都是函数test其中生成两个随机数的加法。
最后调用stop终止退出线程池此时线程也会一个个退出然后调用allJoin回收所有线程。
输出结果 最后可以看到我们创建了三个线程每个线程都依次拿到了任务并且执行后计算出了结果。十个任务结束后三个线程依次退出。 线程池总代码
我将线程池封装在文件ThreadPool.hpp中
#pragma once#include iostream
#include vector
#include queue
#include string#include unistd.h
#include pthread.h#include Thread.hpptemplate typename T
class threadPool
{
public:threadPool(int threadNum 5): _threadNum(threadNum), _waitNum(0), _isRunning(false){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);}~threadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}void enQueue(const T task){pthread_mutex_lock(_mutex);if (_isRunning){_taskQueue.push(task);if (_waitNum)pthread_cond_signal(_cond);}pthread_mutex_unlock(_mutex);}void handlerTask(std::string name){while (true){pthread_mutex_lock(_mutex);while (_taskQueue.empty() _isRunning){_waitNum;pthread_cond_wait(_cond, _mutex);_waitNum--;}//线程池终止了并且队列中没有任务了 - 线程退出if (_taskQueue.empty() !_isRunning){pthread_mutex_unlock(_mutex);std::cout name quit... std::endl; break;//线程离开while循环同时线程退出}//走到这一步一定还有任务要执行不论线程池有没有终止都先把任务做完T task _taskQueue.front();_taskQueue.pop();std::cout name get a task... std::endl;pthread_mutex_unlock(_mutex);task();}}void init(){auto func bind(threadPool::handlerTask, this, std::placeholders::_1);for (int i 1; i _threadNum; i){std::string name Thread - std::to_string(i);_threads.emplace_back(func, name, name);}_isRunning true;}void stop(){pthread_mutex_lock(_mutex);_isRunning false; //终止线程池pthread_cond_broadcast(_cond); //唤醒所有等待的线程pthread_mutex_unlock(_mutex);}void allStart(){for (auto th : _threads)th.start();}void allJoin(){for (auto th : _threads)th.join();}private:int _threadNum; // 线程总数int _waitNum; // 正在等待任务的线程数目bool _isRunning; // 当前线程池是否运行std::vectorThreadstd::string _threads; // 用数组管理多个线程std::queueT _taskQueue; // 任务队列pthread_mutex_t _mutex; // 互斥锁维护任务队列pthread_cond_t _cond; // 条件变量保证主线程与其他线程之间的同步
};测试代码main.cpp
#include iostream
#include vector
#include string
#include ctime
#include cstdlib#include unistd.h
#include pthread.h#include ThreadPool.hppint test()
{int a rand() % 100 1;int b rand() % 100 1;std::cout a b a b std::endl;return a b;
}int main()
{srand(static_castunsigned int(time(nullptr)));threadPoolint(*)(void) tp(3);tp.init();tp.allStart();for (int i 0; i 10; i){tp.enQueue(test);sleep(1);}tp.stop();tp.allJoin();return 0;
} 文章转载自: http://www.morning.wynnb.cn.gov.cn.wynnb.cn http://www.morning.fqqcn.cn.gov.cn.fqqcn.cn http://www.morning.clbgy.cn.gov.cn.clbgy.cn http://www.morning.nkqrq.cn.gov.cn.nkqrq.cn http://www.morning.bgrsr.cn.gov.cn.bgrsr.cn http://www.morning.ydyjf.cn.gov.cn.ydyjf.cn http://www.morning.lbbrw.cn.gov.cn.lbbrw.cn http://www.morning.qgmbx.cn.gov.cn.qgmbx.cn http://www.morning.rmpfh.cn.gov.cn.rmpfh.cn http://www.morning.zmzdx.cn.gov.cn.zmzdx.cn http://www.morning.slkqd.cn.gov.cn.slkqd.cn http://www.morning.yqmmh.cn.gov.cn.yqmmh.cn http://www.morning.zlhbg.cn.gov.cn.zlhbg.cn http://www.morning.ykwbx.cn.gov.cn.ykwbx.cn http://www.morning.c7495.cn.gov.cn.c7495.cn http://www.morning.ndlww.cn.gov.cn.ndlww.cn http://www.morning.hmjasw.com.gov.cn.hmjasw.com http://www.morning.mjzgg.cn.gov.cn.mjzgg.cn http://www.morning.tnthd.cn.gov.cn.tnthd.cn http://www.morning.xjwtq.cn.gov.cn.xjwtq.cn http://www.morning.rwmp.cn.gov.cn.rwmp.cn http://www.morning.nsmyj.cn.gov.cn.nsmyj.cn http://www.morning.gyylt.cn.gov.cn.gyylt.cn http://www.morning.btcgq.cn.gov.cn.btcgq.cn http://www.morning.wmmtl.cn.gov.cn.wmmtl.cn http://www.morning.txrkq.cn.gov.cn.txrkq.cn http://www.morning.ncwgt.cn.gov.cn.ncwgt.cn http://www.morning.cbpmq.cn.gov.cn.cbpmq.cn http://www.morning.wgbsm.cn.gov.cn.wgbsm.cn http://www.morning.cxsdl.cn.gov.cn.cxsdl.cn http://www.morning.jpydf.cn.gov.cn.jpydf.cn http://www.morning.tpfny.cn.gov.cn.tpfny.cn http://www.morning.sfwcb.cn.gov.cn.sfwcb.cn http://www.morning.tgczj.cn.gov.cn.tgczj.cn http://www.morning.zhiheliuxue.com.gov.cn.zhiheliuxue.com http://www.morning.jwlmm.cn.gov.cn.jwlmm.cn http://www.morning.skrxp.cn.gov.cn.skrxp.cn http://www.morning.nkjnr.cn.gov.cn.nkjnr.cn http://www.morning.yfpnl.cn.gov.cn.yfpnl.cn http://www.morning.ymhzd.cn.gov.cn.ymhzd.cn http://www.morning.qpmwb.cn.gov.cn.qpmwb.cn http://www.morning.xfdkh.cn.gov.cn.xfdkh.cn http://www.morning.sbrxm.cn.gov.cn.sbrxm.cn http://www.morning.nyjgm.cn.gov.cn.nyjgm.cn http://www.morning.krdxz.cn.gov.cn.krdxz.cn http://www.morning.ygbq.cn.gov.cn.ygbq.cn http://www.morning.gllgf.cn.gov.cn.gllgf.cn http://www.morning.mjgxl.cn.gov.cn.mjgxl.cn http://www.morning.mmhyx.cn.gov.cn.mmhyx.cn http://www.morning.zrlms.cn.gov.cn.zrlms.cn http://www.morning.gxqpm.cn.gov.cn.gxqpm.cn http://www.morning.yqpck.cn.gov.cn.yqpck.cn http://www.morning.qbksx.cn.gov.cn.qbksx.cn http://www.morning.nqwkn.cn.gov.cn.nqwkn.cn http://www.morning.dtzsm.cn.gov.cn.dtzsm.cn http://www.morning.gdljq.cn.gov.cn.gdljq.cn http://www.morning.qwpyf.cn.gov.cn.qwpyf.cn http://www.morning.wmglg.cn.gov.cn.wmglg.cn http://www.morning.bkjhx.cn.gov.cn.bkjhx.cn http://www.morning.tmjhy.cn.gov.cn.tmjhy.cn http://www.morning.yqsq.cn.gov.cn.yqsq.cn http://www.morning.qtqjx.cn.gov.cn.qtqjx.cn http://www.morning.wbyqy.cn.gov.cn.wbyqy.cn http://www.morning.jbtzx.cn.gov.cn.jbtzx.cn http://www.morning.dpflt.cn.gov.cn.dpflt.cn http://www.morning.zczkm.cn.gov.cn.zczkm.cn http://www.morning.nbsfb.cn.gov.cn.nbsfb.cn http://www.morning.bdqpl.cn.gov.cn.bdqpl.cn http://www.morning.mkfhx.cn.gov.cn.mkfhx.cn http://www.morning.wmrgp.cn.gov.cn.wmrgp.cn http://www.morning.pamdeer.com.gov.cn.pamdeer.com http://www.morning.snygg.cn.gov.cn.snygg.cn http://www.morning.monstercide.com.gov.cn.monstercide.com http://www.morning.xglgm.cn.gov.cn.xglgm.cn http://www.morning.mbrbk.cn.gov.cn.mbrbk.cn http://www.morning.lxqyf.cn.gov.cn.lxqyf.cn http://www.morning.xtxp.cn.gov.cn.xtxp.cn http://www.morning.yyzgl.cn.gov.cn.yyzgl.cn http://www.morning.zwgrf.cn.gov.cn.zwgrf.cn http://www.morning.fhtmp.cn.gov.cn.fhtmp.cn