中国建设银行云南官网站纪念币,万户做网站怎么样,一个空间多个网站,石家庄 科技 公司 网站建设#x1f387;C学习历程#xff1a;入门 博客主页#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话#xff1a; 也许你现在做的事情#xff0c;暂时看不到成果#xff0c;但不要忘记… C学习历程入门 博客主页一起去看日落吗持续分享博主的C学习历程博主的能力有限出现错误希望大家不吝赐教分享给大家一句我很喜欢的话 也许你现在做的事情暂时看不到成果但不要忘记树成长之前也要扎根也要在漫长的时光中沉淀养分。静下来想一想哪有这么多的天赋异禀那些让你羡慕的优秀的人也都曾默默地翻山越岭。 目录1. 大于256KB的大块内存申请问题2. 使用定长内存池配合脱离使用new3. 释放对象时优化为不传对象大小4. 多线程环境下对比malloc测试5. 复杂问题的调试技巧1. 大于256KB的大块内存申请问题
申请过程
每个线程的thread cache是用于申请小于等于256KB的内存的而对于大于256KB的内存我们可以考虑直接向page cache申请但page cache中最大的页也就只有128页因此如果是大于128页的内存申请就只能直接向堆申请了。
申请内存的大小申请方式x ≤ 256 K B ( 32 页 )向thread cache申请32 页 x ≤ 128 页向page cache申请x ≥ 128 页向堆申请
当申请的内存大于256KB时虽然不是从thread cache进行获取但在分配内存时也是需要进行向上对齐的对于大于256KB的内存我们可以直接按页进行对齐。
而我们之前实现RoundUp函数时对传入字节数大于256KB的情况直接做了断言处理因此这里需要对RoundUp函数稍作修改。
//获取向上对齐后的字节数
static inline size_t RoundUp(size_t bytes)
{if (bytes 128){return _RoundUp(bytes, 8);}else if (bytes 1024){return _RoundUp(bytes, 16);}else if (bytes 8 * 1024){return _RoundUp(bytes, 128);}else if (bytes 64 * 1024){return _RoundUp(bytes, 1024);}else if (bytes 256 * 1024){return _RoundUp(bytes, 8 * 1024);}else{//大于256KB的按页对齐return _RoundUp(bytes, 1 PAGE_SHIFT);}
}
现在对于之前的申请逻辑就需要进行修改了当申请对象的大小大于256KB时就不用向thread cache申请了这时先计算出按页对齐后实际需要申请的页数然后通过调用NewSpan申请指定页数的span即可。
static void* ConcurrentAlloc(size_t size)
{if (size MAX_BYTES) //大于256KB的内存申请{//计算出对齐后需要申请的页数size_t alignSize SizeClass::RoundUp(size);size_t kPage alignSize PAGE_SHIFT;//向page cache申请kPage页的spanPageCache::GetInstance()-_pageMtx.lock();Span* span PageCache::GetInstance()-NewSpan(kPage);PageCache::GetInstance()-_pageMtx.unlock();void* ptr (void*)(span-_pageId PAGE_SHIFT);return ptr;}else{//通过TLS每个线程无锁的获取自己专属的ThreadCache对象if (pTLSThreadCache nullptr){pTLSThreadCache new ThreadCache;}cout std::this_thread::get_id() : pTLSThreadCache endl;return pTLSThreadCache-Allocate(size);}
}也就是说申请大于256KB的内存时会直接调用page cache当中的NewSpan函数进行申请因此这里我们需要再对NewSpan函数进行改造当需要申请的内存页数大于128页时就直接向堆申请对应页数的内存块。而如果申请的内存页数是小于128页的那就在page cache中进行申请因此当申请大于256KB的内存调用NewSpan函数时也是需要加锁的因为我们可能是在page cache中进行申请的。
//获取一个k页的span
Span* PageCache::NewSpan(size_t k)
{assert(k 0);if (k NPAGES - 1) //大于128页直接找堆申请{void* ptr SystemAlloc(k);Span* span new Span;span-_pageId (PAGE_ID)ptr PAGE_SHIFT;span-_n k;//建立页号与span之间的映射_idSpanMap[span-_pageId] span;return span;}//先检查第k个桶里面有没有spanif (!_spanLists[k].Empty()){Span* kSpan _spanLists[k].PopFront();//建立页号与span的映射方便central cache回收小块内存时查找对应的spanfor (PAGE_ID i 0; i kSpan-_n; i){_idSpanMap[kSpan-_pageId i] kSpan;}return kSpan;}//检查一下后面的桶里面有没有span如果有可以将其进行切分for (size_t i k 1; i NPAGES; i){if (!_spanLists[i].Empty()){Span* nSpan _spanLists[i].PopFront();Span* kSpan new Span;//在nSpan的头部切k页下来kSpan-_pageId nSpan-_pageId;kSpan-_n k;nSpan-_pageId k;nSpan-_n - k;//将剩下的挂到对应映射的位置_spanLists[nSpan-_n].PushFront(nSpan);//存储nSpan的首尾页号与nSpan之间的映射方便page cache合并span时进行前后页的查找_idSpanMap[nSpan-_pageId] nSpan;_idSpanMap[nSpan-_pageId nSpan-_n - 1] nSpan;//建立页号与span的映射方便central cache回收小块内存时查找对应的spanfor (PAGE_ID i 0; i kSpan-_n; i){_idSpanMap[kSpan-_pageId i] kSpan;}return kSpan;}}//走到这里说明后面没有大页的span了这时就向堆申请一个128页的spanSpan* bigSpan new Span;void* ptr SystemAlloc(NPAGES - 1);bigSpan-_pageId (PAGE_ID)ptr PAGE_SHIFT;bigSpan-_n NPAGES - 1;_spanLists[bigSpan-_n].PushFront(bigSpan);//尽量避免代码重复递归调用自己return NewSpan(k);
}释放过程
释放内存的大小释放方式x ≤ 256 K B ( 32 页 )释放给thread cache32 页 x ≤ 128 页释放给page cachex ≥ 128 页释放给堆
因此当释放对象时我们需要先找到该对象对应的span但是在释放对象时我们只知道该对象的起始地址。这也就是我们在申请大于256KB的内存时也要给申请到的内存建立span结构并建立起始页号与该span之间的映射关系的原因。此时我们就可以通过释放对象的起始地址计算出起始页号进而通过页号找到该对象对应的span。
static void ConcurrentFree(void* ptr, size_t size)
{if (size MAX_BYTES) //大于256KB的内存释放{Span* span PageCache::GetInstance()-MapObjectToSpan(ptr);PageCache::GetInstance()-_pageMtx.lock();PageCache::GetInstance()-ReleaseSpanToPageCache(span);PageCache::GetInstance()-_pageMtx.unlock();}else{assert(pTLSThreadCache);pTLSThreadCache-Deallocate(ptr, size);}
}因此page cache在回收span时也需要进行判断如果该span的大小是小于等于128页的那么直接还给page cache进行了page cache会尝试对其进行合并。而如果该span的大小是大于128页的那么说明该span是直接向堆申请的我们直接将这块内存释放给堆然后将这个span结构进行delete就行了。
//释放空闲的span回到PageCache并合并相邻的span
void PageCache::ReleaseSpanToPageCache(Span* span)
{if (span-_n NPAGES - 1) //大于128页直接释放给堆{void* ptr (void*)(span-_pageId PAGE_SHIFT);SystemFree(ptr);delete span;return;}//对span的前后页尝试进行合并缓解内存碎片问题//1、向前合并while (1){PAGE_ID prevId span-_pageId - 1;auto ret _idSpanMap.find(prevId);//前面的页号没有还未向系统申请停止向前合并if (ret _idSpanMap.end()){break;}//前面的页号对应的span正在被使用停止向前合并Span* prevSpan ret-second;if (prevSpan-_isUse true){break;}//合并出超过128页的span无法进行管理停止向前合并if (prevSpan-_n span-_n NPAGES - 1){break;}//进行向前合并span-_pageId prevSpan-_pageId;span-_n prevSpan-_n;//将prevSpan从对应的双链表中移除_spanLists[prevSpan-_n].Erase(prevSpan);delete prevSpan;}//2、向后合并while (1){PAGE_ID nextId span-_pageId span-_n;auto ret _idSpanMap.find(nextId);//后面的页号没有还未向系统申请停止向后合并if (ret _idSpanMap.end()){break;}//后面的页号对应的span正在被使用停止向后合并Span* nextSpan ret-second;if (nextSpan-_isUse true){break;}//合并出超过128页的span无法进行管理停止向后合并if (nextSpan-_n span-_n NPAGES - 1){break;}//进行向后合并span-_n nextSpan-_n;//将nextSpan从对应的双链表中移除_spanLists[nextSpan-_n].Erase(nextSpan);delete nextSpan;}//将合并后的span挂到对应的双链表当中_spanLists[span-_n].PushFront(span);//建立该span与其首尾页的映射_idSpanMap[span-_pageId] span;_idSpanMap[span-_pageId span-_n - 1] span;//将该span设置为未被使用的状态span-_isUse false;
}说明一下直接向堆申请内存时我们调用的接口是VirtualAlloc与之对应的将内存释放给堆的接口叫做VirtualFree而Linux下的brk和mmap对应的释放接口叫做sbrk和unmmap。此时我们也可以将这些释放接口封装成一个叫做SystemFree的接口当我们需要将内存释放给堆时直接调用SystemFree即可。
//直接将内存还给堆
inline static void SystemFree(void* ptr)
{
#ifdef _WIN32VirtualFree(ptr, 0, MEM_RELEASE);
#else//linux下sbrk unmmap等
#endif
}简单测试
//找page cache申请
void* p1 ConcurrentAlloc(257 * 1024); //257KB
ConcurrentFree(p1, 257 * 1024);//找堆申请
void* p2 ConcurrentAlloc(129 * 8 * 1024); //129页
ConcurrentFree(p2, 129 * 8 * 1024);当申请257KB的内存时由于257KB的内存按页向上对齐后是33页并没有大于128页因此不会直接向堆进行申请会向page cache申请内存但此时page cache当中实际是没有内存的最终page cache就会向堆申请一个128页的span将其切分成33页的span和95页的span并将33页的span进行返回。
而在释放内存时由于该对象的大小大于了256KB因此不会将其还给thread cache而是直接调用的page cache当中的释放接口。
由于该对象的大小是33页不大于128页因此page cache也不会直接将该对象还给堆而是尝试对其进行合并最终就会把这个33页的span和之前剩下的95页的span进行合并最终将合并后的128页的span挂到第128号桶中。
当申请129页的内存时由于是大于256KB的于是还是调用的page cache对应的申请接口但此时申请的内存同时也大于128页因此会直接向堆申请。在申请后还会建立该span与其起始页号之间的映射便于释放时可以通过页号找到该span。
在释放内存时通过对象的地址找到其对应的span从span结构中得知释放内存的大小大于128页于是会将该内存直接还给堆。 2. 使用定长内存池配合脱离使用new
tcmalloc是要在高并发场景下替代malloc进行内存申请的因此tcmalloc在实现的时其内部是不能调用malloc函数的我们当前的代码中存在通过new获取到的内存而new在底层实际上就是封装了malloc。
为了完全脱离掉malloc函数此时我们之前实现的定长内存池就起作用了代码中使用new时基本都是为Span结构的对象申请空间而span对象基本都是在page cache层创建的因此我们可以在PageCache类当中定义一个_spanPool用于span对象的申请和释放。
//单例模式
class PageCache
{
public://...
private:ObjectPoolSpan _spanPool;
};
然后将代码中使用new的地方替换为调用定长内存池当中的New函数将代码中使用delete的地方替换为调用定长内存池当中的Delete函数。
//申请span对象
Span* span _spanPool.New();
//释放span对象
_spanPool.Delete(span);
注意当使用定长内存池当中的New函数申请Span对象时New函数通过定位new也是对Span对象进行了初始化的。
此外每个线程第一次申请内存时都会创建其专属的thread cache而这个thread cache目前也是new出来的我们也需要对其进行替换。
//通过TLS每个线程无锁的获取自己专属的ThreadCache对象
if (pTLSThreadCache nullptr)
{static std::mutex tcMtx;static ObjectPoolThreadCache tcPool;tcMtx.lock();pTLSThreadCache tcPool.New();tcMtx.unlock();
}这里我们将用于申请ThreadCache类对象的定长内存池定义为静态的保持全局只有一个让所有线程创建自己的thread cache时都在个定长内存池中申请内存就行了。
但注意在从该定长内存池中申请内存时需要加锁防止多个线程同时申请自己的ThreadCache对象而导致线程安全问题。 3. 释放对象时优化为不传对象大小
当我们使用malloc函数申请内存时需要指明申请内存的大小而当我们使用free函数释放内存时只需要传入指向这块内存的指针即可。
而我们目前实现的内存池在释放对象时除了需要传入指向该对象的指针还需要传入该对象的大小。 如果释放的是大于256KB的对象需要根据对象的大小来判断这块内存到底应该还给page cache还是应该直接还给堆。 如果释放的是小于等于256KB的对象需要根据对象的大小计算出应该还给thread cache的哪一个哈希桶。
如果我们也想做到在释放对象时不用传入对象的大小那么我们就需要建立对象地址与对象大小之间的映射。由于现在可以通过对象的地址找到其对应的span而span的自由链表中挂的都是相同大小的对象。
因此我们可以在Span结构中再增加一个_objSize成员该成员代表着这个span管理的内存块被切成的一个个对象的大小。
//管理以页为单位的大块内存
struct Span
{PAGE_ID _pageId 0; //大块内存起始页的页号size_t _n 0; //页的数量Span* _next nullptr; //双链表结构Span* _prev nullptr;size_t _objSize 0; //切好的小对象的大小size_t _useCount 0; //切好的小块内存被分配给thread cache的计数void* _freeList nullptr; //切好的小块内存的自由链表bool _isUse false; //是否在被使用
};而所有的span都是从page cache中拿出来的因此每当我们调用NewSpan获取到一个k页的span时就应该将这个span的_objSize保存下来。
Span* span PageCache::GetInstance()-NewSpan(SizeClass::NumMovePage(size));
span-_objSize size;此时当我们释放对象时就可以直接从对象的span中获取到该对象的大小准确来说获取到的是对齐以后的大小。
static void ConcurrentFree(void* ptr)
{Span* span PageCache::GetInstance()-MapObjectToSpan(ptr);size_t size span-_objSize;if (size MAX_BYTES) //大于256KB的内存释放{PageCache::GetInstance()-_pageMtx.lock();PageCache::GetInstance()-ReleaseSpanToPageCache(span);PageCache::GetInstance()-_pageMtx.unlock();}else{assert(pTLSThreadCache);pTLSThreadCache-Deallocate(ptr, size);}
}读取映射关系时的加锁问题
我们将页号与span之间的映射关系是存储在PageCache类当中的当我们访问这个映射关系时是需要加锁的因为STL容器是不保证线程安全的。
对于当前代码来说如果我们此时正在page cache进行相关操作那么访问这个映射关系是安全的因为当进入page cache之前是需要加锁的因此可以保证此时只有一个线程在进行访问。
但如果我们是在central cache访问这个映射关系或是在调用ConcurrentFree函数释放内存时访问这个映射关系那么就存在线程安全的问题。因为此时可能其他线程正在page cache当中进行某些操作并且该线程此时可能也在访问这个映射关系因此当我们在page cache外部访问这个映射关系时是需要加锁的。
实际就是在调用page cache对外提供访问映射关系的函数时需要加锁这里我们可以考虑使用C当中的unique_lock当然你也可以用普通的锁。
//获取从对象到span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id (PAGE_ID)obj PAGE_SHIFT; //页号std::unique_lockstd::mutex lock(_pageMtx); //构造时加锁析构时自动解锁auto ret _idSpanMap.find(id);if (ret ! _idSpanMap.end()){return ret-second;}else{assert(false);return nullptr;}
} 4. 多线程环境下对比malloc测试
之前我们只是对代码进行了一些基础的单元测试下面我们在多线程场景下对比malloc进行测试。
void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds)
{std::vectorstd::thread vthread(nworks);std::atomicsize_t malloc_costtime 0;std::atomicsize_t free_costtime 0;for (size_t k 0; k nworks; k){vthread[k] std::thread([, k]() {std::vectorvoid* v;v.reserve(ntimes);for (size_t j 0; j rounds; j){size_t begin1 clock();for (size_t i 0; i ntimes; i){v.push_back(malloc(16));//v.push_back(malloc((16 i) % 8192 1));}size_t end1 clock();size_t begin2 clock();for (size_t i 0; i ntimes; i){free(v[i]);}size_t end2 clock();v.clear();malloc_costtime (end1 - begin1);free_costtime (end2 - begin2);}});}for (auto t : vthread){t.join();}printf(%u个线程并发执行%u轮次每轮次malloc %u次: 花费%u ms\n,nworks, rounds, ntimes, malloc_costtime);printf(%u个线程并发执行%u轮次每轮次free %u次: 花费%u ms\n,nworks, rounds, ntimes, free_costtime);printf(%u个线程并发mallocfree %u次总计花费%u ms\n,nworks, nworks*rounds*ntimes, malloc_costtime free_costtime);
}void BenchmarkConcurrentMalloc(size_t ntimes, size_t nworks, size_t rounds)
{std::vectorstd::thread vthread(nworks);std::atomicsize_t malloc_costtime 0;std::atomicsize_t free_costtime 0;for (size_t k 0; k nworks; k){vthread[k] std::thread([]() {std::vectorvoid* v;v.reserve(ntimes);for (size_t j 0; j rounds; j){size_t begin1 clock();for (size_t i 0; i ntimes; i){v.push_back(ConcurrentAlloc(16));//v.push_back(ConcurrentAlloc((16 i) % 8192 1));}size_t end1 clock();size_t begin2 clock();for (size_t i 0; i ntimes; i){ConcurrentFree(v[i]);}size_t end2 clock();v.clear();malloc_costtime (end1 - begin1);free_costtime (end2 - begin2);}});}for (auto t : vthread){t.join();}printf(%u个线程并发执行%u轮次每轮次concurrent alloc %u次: 花费%u ms\n,nworks, rounds, ntimes, malloc_costtime);printf(%u个线程并发执行%u轮次每轮次concurrent dealloc %u次: 花费%u ms\n,nworks, rounds, ntimes, free_costtime);printf(%u个线程并发concurrent allocdealloc %u次总计花费%u ms\n,nworks, nworks*rounds*ntimes, malloc_costtime free_costtime);
}int main()
{size_t n 10000;cout endl;BenchmarkConcurrentMalloc(n, 4, 10);cout endl endl;BenchmarkMalloc(n, 4, 10);cout endl;return 0;
}其中测试函数各个参数的含义如下
ntimes单轮次申请和释放内存的次数。nworks线程数。rounds轮次。
在测试函数中我们通过clock函数分别获取到每轮次申请和释放所花费的时间然后将其对应累加到malloc_costtime和free_costtime上。最后我们就得到了nworks个线程跑rounds轮每轮申请和释放ntimes次这个过程申请所消耗的时间、释放所消耗的时间、申请和释放总共消耗的时间。
注意我们创建线程时让线程执行的是lambda表达式而我们这里在使用lambda表达式时以值传递的方式捕捉了变量k以引用传递的方式捕捉了其他父作用域中的变量因此我们可以将各个线程消耗的时间累加到一起。
我们将所有线程申请内存消耗的时间都累加到malloc_costtime上 将释放内存消耗的时间都累加到free_costtime上此时malloc_costtime和free_costtime可能被多个线程同时进行累加操作的所以存在线程安全的问题。鉴于此我们在定义这两个变量时使用了atomic类模板这时对它们的操作就是原子操作。
固定大小内存的申请和释放
此时4个线程执行10轮操作每轮申请释放10000次总共申请释放了40万次运行后可以看到malloc的效率还是更高的。 由于此时我们申请释放的都是固定大小的对象每个线程申请释放时访问的都是各自thread cache的同一个桶当thread cache的这个桶中没有对象或对象太多要归还时也都会访问central cache的同一个桶。此时central cache中的桶锁就不起作用了因为我们让central cache使用桶锁的目的就是为了让多个thread cache可以同时访问central cache的不同桶而此时每个thread cache访问的却都是central cache中的同一个桶。
不同大小内存的申请和释放
运行后可以看到由于申请和释放内存的大小是不同的此时central cache当中的桶锁就起作用了ConcurrentAlloc的效率也有了较大增长但相比malloc来说还是差一点点。 5. 复杂问题的调试技巧
多线程调试比单线程调试要复杂得多调试时各个线程之间会相互切换并且每次调试切换的时机也是不固定的这就使得调试过程变得非常难以控制。
下面给出三个调试时的小技巧
条件断点
一般情况下我们可以直接运行程序通过报错来查找问题。如果此时报的是断言错误那么我们可以直接定位到报错的位置然后将此处的断言改为与断言条件相反的if判断在if语句里面打上一个断点但注意空语句是无法打断点的这时我们随便在if里面加上一句代码就可以打断点了。
if(pos _head)
{int x 0;//随意写
}查看函数栈帧
当程序运行到断点处时我们需要对当前位置进行分析如果检查后发现当前函数是没有问题的这时可能需要回到调用该函数的地方进行进一步分析此时我们可以依次点击“调试→窗口→调用堆栈”。
疑似死循环时中断程序
当你在某个地方设置断点后如果迟迟没有运行到断点处而程序也没有崩溃这时有可能是程序进入到某个死循环了。
这时我们可以依次点击“调试→全部中断”。
这时程序就会在当前运行的地方停下来。
文章转载自: http://www.morning.muzishu.com.gov.cn.muzishu.com http://www.morning.ktxd.cn.gov.cn.ktxd.cn http://www.morning.bhdyr.cn.gov.cn.bhdyr.cn http://www.morning.zzqgc.cn.gov.cn.zzqgc.cn http://www.morning.wtcyz.cn.gov.cn.wtcyz.cn http://www.morning.fxxmj.cn.gov.cn.fxxmj.cn http://www.morning.swdnr.cn.gov.cn.swdnr.cn http://www.morning.prxqd.cn.gov.cn.prxqd.cn http://www.morning.bljcb.cn.gov.cn.bljcb.cn http://www.morning.yccnj.cn.gov.cn.yccnj.cn http://www.morning.wwgpy.cn.gov.cn.wwgpy.cn http://www.morning.ohmyjiu.com.gov.cn.ohmyjiu.com http://www.morning.lsyk.cn.gov.cn.lsyk.cn http://www.morning.ltrms.cn.gov.cn.ltrms.cn http://www.morning.djwpd.cn.gov.cn.djwpd.cn http://www.morning.bmhc.cn.gov.cn.bmhc.cn http://www.morning.ngzkt.cn.gov.cn.ngzkt.cn http://www.morning.grnhb.cn.gov.cn.grnhb.cn http://www.morning.hctgn.cn.gov.cn.hctgn.cn http://www.morning.fqnql.cn.gov.cn.fqnql.cn http://www.morning.mxcgf.cn.gov.cn.mxcgf.cn http://www.morning.simpliq.cn.gov.cn.simpliq.cn http://www.morning.xdhcr.cn.gov.cn.xdhcr.cn http://www.morning.pslzp.cn.gov.cn.pslzp.cn http://www.morning.lwgrf.cn.gov.cn.lwgrf.cn http://www.morning.gwjqq.cn.gov.cn.gwjqq.cn http://www.morning.sqlh.cn.gov.cn.sqlh.cn http://www.morning.srbbh.cn.gov.cn.srbbh.cn http://www.morning.chehb.com.gov.cn.chehb.com http://www.morning.fhrgk.cn.gov.cn.fhrgk.cn http://www.morning.zlwg.cn.gov.cn.zlwg.cn http://www.morning.xhddb.cn.gov.cn.xhddb.cn http://www.morning.gbybx.cn.gov.cn.gbybx.cn http://www.morning.swlwf.cn.gov.cn.swlwf.cn http://www.morning.qwdqq.cn.gov.cn.qwdqq.cn http://www.morning.hlwzd.cn.gov.cn.hlwzd.cn http://www.morning.ktqtf.cn.gov.cn.ktqtf.cn http://www.morning.nypsz.cn.gov.cn.nypsz.cn http://www.morning.knzdt.cn.gov.cn.knzdt.cn http://www.morning.fpzz1.cn.gov.cn.fpzz1.cn http://www.morning.ydyjf.cn.gov.cn.ydyjf.cn http://www.morning.sgcdr.com.gov.cn.sgcdr.com http://www.morning.gyfwy.cn.gov.cn.gyfwy.cn http://www.morning.ebpz.cn.gov.cn.ebpz.cn http://www.morning.ntqqm.cn.gov.cn.ntqqm.cn http://www.morning.kflbf.cn.gov.cn.kflbf.cn http://www.morning.bdqpl.cn.gov.cn.bdqpl.cn http://www.morning.xdxpq.cn.gov.cn.xdxpq.cn http://www.morning.kgnnc.cn.gov.cn.kgnnc.cn http://www.morning.nhdw.cn.gov.cn.nhdw.cn http://www.morning.pynzj.cn.gov.cn.pynzj.cn http://www.morning.yjmlg.cn.gov.cn.yjmlg.cn http://www.morning.hfyll.cn.gov.cn.hfyll.cn http://www.morning.ndtzy.cn.gov.cn.ndtzy.cn http://www.morning.rjjys.cn.gov.cn.rjjys.cn http://www.morning.qzzmc.cn.gov.cn.qzzmc.cn http://www.morning.mqnbm.cn.gov.cn.mqnbm.cn http://www.morning.bnwlh.cn.gov.cn.bnwlh.cn http://www.morning.kqlrl.cn.gov.cn.kqlrl.cn http://www.morning.kfldw.cn.gov.cn.kfldw.cn http://www.morning.zfgh.cn.gov.cn.zfgh.cn http://www.morning.wdply.cn.gov.cn.wdply.cn http://www.morning.gcdzp.cn.gov.cn.gcdzp.cn http://www.morning.ndxmn.cn.gov.cn.ndxmn.cn http://www.morning.yqrfn.cn.gov.cn.yqrfn.cn http://www.morning.dnls.cn.gov.cn.dnls.cn http://www.morning.jcnmy.cn.gov.cn.jcnmy.cn http://www.morning.tcsdlbt.cn.gov.cn.tcsdlbt.cn http://www.morning.bybhj.cn.gov.cn.bybhj.cn http://www.morning.nrtpb.cn.gov.cn.nrtpb.cn http://www.morning.pfbx.cn.gov.cn.pfbx.cn http://www.morning.kkzwn.cn.gov.cn.kkzwn.cn http://www.morning.qsy41.cn.gov.cn.qsy41.cn http://www.morning.hfnbr.cn.gov.cn.hfnbr.cn http://www.morning.ygth.cn.gov.cn.ygth.cn http://www.morning.dybth.cn.gov.cn.dybth.cn http://www.morning.sqqdy.cn.gov.cn.sqqdy.cn http://www.morning.bylzr.cn.gov.cn.bylzr.cn http://www.morning.ey3h2d.cn.gov.cn.ey3h2d.cn http://www.morning.kjdxh.cn.gov.cn.kjdxh.cn