当前位置: 首页 > news >正文

广西建设科技与建筑节能协会网站wordpress被镜像

广西建设科技与建筑节能协会网站,wordpress被镜像,点商城域名注册多少钱,深圳市网站建设科技目录 一、线程互斥1.相关概念2.互斥锁#xff08;mutex#xff09;3.互斥锁的原理4.自定义封装一个锁 二、可重入和线程安全三、死锁死锁概念死锁四个必要条件如何避免死锁 四、线程同步1.条件变量概念条件变量接口基于阻塞队列实现生产者消费者模型 2.信号量概念信号量操作接… 目录 一、线程互斥1.相关概念2.互斥锁mutex3.互斥锁的原理4.自定义封装一个锁 二、可重入和线程安全三、死锁死锁概念死锁四个必要条件如何避免死锁 四、线程同步1.条件变量概念条件变量接口基于阻塞队列实现生产者消费者模型 2.信号量概念信号量操作接口基于环形队列的生产者消费者模型 五、总结 一、线程互斥 1.相关概念 1.临界资源多线程执行流共享的资源且一次只能允许一个执行流访问的资源就叫做临界资源。(多线程、多进程打印数据) 2.临界区每个线程内部访问临界资源的代码就叫做临界区。 3.互斥任何时刻互斥保证有且只有一个执行流进入临界区访问临界资源通常对临界资源起保护作用。 4.原子性不会被任何调度机制打断的操作该操作只有两态要么完成要么不执行 。 实现一个小实例 #includeiostream #includeunistd.h #includestdio.husing namespace std; int ticket10000;void* threadRoutinue(void* args) {const char* namestatic_castconst char*(args);while(true){if(ticket0){usleep(1000);//模拟抢票花费时间coutnameget a ticket: ticket--endl;}else{break;}}return nullptr; }int main() {//创建线程模拟抢票pthread_t tid[4];int nsizeof(tid)/sizeof(tid[0]);for(int i0;in;i){char buffer[64];snprintf(buffer,sizeof(buffer),thread_%d,i);pthread_create(tidi,nullptr,threadRoutinue,buffer);}for(int i0;in;i){pthread_join(tid[i],nullptr);}return 0; } 从程序中可以看到票数到0的时候就没有票了线程就应该退出了。 但是结果中票数甚至被抢到了负数这是怎么回事。 这里提一个问题这里对票(临界资源)的访问是原子的吗(是安全的吗) 答案肯定不是 可能在一个线程A中刚刚将tickets加载到内存上线程A就被切走了这时线程A的数据和上下文被保存线程A从CPU上被剥离。 线程B开始抢票如果他的竞争力非常强一次运行后抢到了1000张票。 线程B执行完后线程A又来了他会从上次执行的地方继续执行但是他上次保存的tickets的数据是10000所以抢到了一张票后将剩余的9999张票写回内存本来线程B执行完后还剩9000张票但是线程A执行完后剩余的票数反而增多了。 2.互斥锁mutex 对于上面的抢票程序要想使每个线程正确的抢票就要保证当一个线程在进入到抢票环节时其他线程不能进行抢票。 所以就可以对抢票环节加互斥锁。 pthread_mutex_init、pthread_mutex_destroy对线程锁进行初始化和销毁 #include pthread.h // pthread_mutex_t mutex: 锁变量所有线程都可看到 int pthread_mutex_destroy(pthread_mutex_t *mutex);// 销毁锁 int pthread_mutex_init(pthread_mutex_t *restrict mutex,constpthread_mutexattr_t *restrict attr);// 初始化锁 // attr: 锁属性我们传入空指针就可 // 如果将锁定义为静态或者全局的可以使用宏直接初始化且不用销毁 pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock、int pthread_mutex_unlock对线程进行加锁和解锁 #include pthread.h int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 对抢票小demo进行加锁 #includeiostream #includeunistd.h #includestdio.husing namespace std; int ticket10000; //临界资源 pthread_mutex_t mutex;void* threadRoutinue(void* args) {const char* namestatic_castconst char*(args);while(true){pthread_mutex_lock(mutex);if(ticket0){usleep(1000);//模拟抢票花费时间coutname get a ticket: ticket--endl;pthread_mutex_unlock(mutex);}else{coutname票抢完了endl;pthread_mutex_unlock(mutex);break;}usleep(1000);}return nullptr; }int main() {pthread_mutex_init(mutex,nullptr);//创建线程模拟抢票pthread_t tid[4];int nsizeof(tid)/sizeof(tid[0]);for(int i0;in;i){char buffer[64];snprintf(buffer,sizeof(buffer),thread_%d,i);pthread_create(tidi,nullptr,threadRoutinue,buffer);}for(int i0;in;i){pthread_join(tid[i],nullptr);}pthread_mutex_destroy(mutex); return 0; } 多线程临界资源原子 细节 1.凡是访问同一个临界资源的线程都要进行加锁保护而且必须加同一把锁这是一个规则不能有例外 2.每一个线程访问临界资源之前得加锁加锁本质是给 临界区加锁加锁的粒度尽量细一些。 3.线程访问临界区的时候需要先加锁 - 所以线程都必须看到同一把锁 - 锁本身就是公共资源 - 锁如何保证自己的安全 - 加锁和解锁本身就是原子的。 4.临界区可以是一行代码可以是一批代码a.线程可能被切换 当然可能 b.切换会有影响嘛 没有因为一个线程申请一个锁以后该线程被临时切换其他任何线程没有办法进入临界区无法申请到锁所以无法访问到临界资源。 5.这也正是体现互斥带来的串行化的表现站在其他线程的角度对其他线程有意义的状态是锁被申请持有锁锁被释放不持有锁原子性。 3.互斥锁的原理 以抢票程序为例当线程需要访问临界资源时需要先访问mtx为了所有的线程都能看到它所以锁肯定是全局的。 且锁本身也是临界资源。那么如何保证锁本身是安全的即获取锁的过程是安全的。 其原理是加锁(lock)、解锁(unlock)的过程是原子的 那怎样才算是原子的呢一行代码被翻译成汇编后只有一条汇编就是原子的。 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令。 该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性。 即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 当线程申请到锁之后进入到临界区访问临界资源这时线程也可能被切走被切走后会保护上下文而锁数据也在上下文中。 所以锁也被带走了所以即便是该线程被挂起了其他线程也不能申请到锁也不能进入临界区。 必须等待拥有锁的线程释放锁之后才能申请到锁。 4.自定义封装一个锁 #pragma once #includeiostream #includepthread.h//封装锁class _Mutex { public:_Mutex(pthread_mutex_t* mutex):_mutex(mutex){}void lock(){pthread_mutex_lock(_mutex);}void unlock(){pthread_mutex_unlock(_mutex);} private:pthread_mutex_t* _mutex; };class lockGuard { public:lockGuard(pthread_mutex_t* mutex):_mutex(mutex){_mutex.lock();}~lockGuard(){_mutex.unlock();}private:_Mutex _mutex; };我们可以使用我们自己封装的锁解决抢票问题 二、可重入和线程安全 线程安全 线程安全指的是在多线程编程中多个线程对临界资源进行争抢访问而不会造成数据二义或程序逻辑混乱的情况。常见对全局变量或者静态变量进行操作并且没有锁保护的情况下会出现该问题。 重入 同一个函数被不同的执行流调用当前一个流程还没有执行完就有其他的执行流再次进入我们称之为重入。一个函数在重入的情况下运行结果不会出现任何不同或者任何问题则该函数被称为可重入函数否则是不可重入函数 线程安全的实现通过同步与互斥实现 具体互斥的实现可以通过互斥锁和信号量实现、而同步可以通过条件变量与信号量实现。 常见的线程不安全的情况 不保护共享变量的函数 函数状态随着被调用状态发生变化的函数 返回指向静态变量指针的函数 调用线程不安全函数的函数 常见不可重入的情况 调用了malloc/free函数因为malloc函数是用全局链表来管理堆的 调用了标准I/O库函数标准I/O库的很多实现都以不可重入的方式使用全局数据结构 可重入函数体内使用了静态的数据结构 可重入与线程安全联系: 函数是可重入的那就是线程安全的 函数是不可重入的那就不能由多个线程使用有可能引发线程安全问题 如果一个函数中有全局变量那么这个函数既不是线程安全也不是可重入的。 可重入与线程安全区别: 可重入函数是线程安全函数的一种 线程安全不一定是可重入的而可重入函数则一定是线程安全的。 如果将对临界资源的访问加上锁则这个函数是线程安全的但如果这个重入函数锁还未释放则会产生死锁 三、死锁 死锁概念 死锁是指在一组进程中的各个进程均占有不会释放的资源但因互相申请被其他进程所占用不会释放的资 源而处于的一种永久等待状态。 死锁四个必要条件 互斥条件一个资源每次只能被一个执行流使用 请求与保持条件一个执行流因请求资源而阻塞时对已获得的资源保持不放 不剥夺条件:一个执行流已获得的资源在末使用完之前不能强行剥夺 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系 如何避免死锁 核心思想破坏死锁的4个必要条件中任意一个 不加锁 主动释放锁 按顺序申请锁 资源一次性分配 破坏死锁的一个小demo主动释放锁 #include iostream #includeunistd.h #include pthread.husing namespace std;pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;void *pthreadRoutinue(void *args) { pthread_mutex_lock(mutex); //加锁coutI get a mutexendl;pthread_mutex_lock(mutex); //产生死锁couti alive againendl;return nullptr; }int main() {pthread_t pid;pthread_create(pid, nullptr, pthreadRoutinue, nullptr);sleep(3);coutmain thread runendl;pthread_mutex_unlock(mutex);//主线程区解锁coutmain thread unlockendl;sleep(3);return 0; } 四、线程同步 同步在保证数据安全的前提下让线程能够按照某种特定的顺序访问临界资源从而有效避免饥饿问 题叫做同步 。 1.条件变量 概念 与互斥锁不同条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。 条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制主要包括两个动作一个线程等待条件变量的条件成立而挂起另一个线程使条件成立给出条件成立信号。 条件变量接口 pthread_cond_init、pthread_cond_destroy初始化、销毁条件变量 #include pthread.h int pthread_cond_destroy(pthread_cond_t *cond); // pthread_cond_t:条件变量类型类似pthread_mutex_t int pthread_cond_init(pthread_cond_t *restrict cond,constpthread_condattr_t *restrict attr); // 如果是静态或全局的条件变量可使用宏初始化 pthread_cond_t cond PTHREAD_COND_INITIALIZER; pthread_cond_wait、pthread_cond_signal等待条件、唤醒线程 #include pthread.h // 等待条件满足 int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); // 唤醒一个线程在cond等待队列里的第一个线程 int pthread_cond_signal(pthread_cond_t *cond); // 一次唤醒所有线程 int pthread_cond_broadcast(pthread_cond_t *cond); demo #include iostream #includeunistd.h #include pthread.husing namespace std; #define num 5int ticket 1000; pthread_mutex_t mutexPTHREAD_MUTEX_INITIALIZER; pthread_cond_t condPTHREAD_COND_INITIALIZER;void *active(void *args) { string namestatic_castconst char*(args);while(true){pthread_mutex_lock(mutex);pthread_cond_wait(cond,mutex); //调用该函数会自己释放锁coutname 活动endl;pthread_mutex_unlock(mutex);}return nullptr; }int main() {pthread_t tids[num];for(int i0;inum;i){char * namenew char[64];snprintf(name,64,thread-%d,i); //线程创pthread_create(tidsi,nullptr,active,name);}sleep(3);while(true){coutmain thread wakeup thread...endl;//pthread_cond_signal(cond); //唤醒cond队列中的一个线程pthread_cond_broadcast(cond); //将cond队列中所以线程唤醒sleep(1);}for(int i0;inum;i){pthread_join(tids[i],nullptr); //线程等待}sleep(3);return 0; } 基于阻塞队列实现生产者消费者模型 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。 生产者和消费者彼此之间不直接通讯而通过阻塞队列来进行通讯所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。 生产者消费者模型的优点解耦 支持并发 支持忙闲不均 实则之前所讲的进程间通信中的管道通信就是一种生产者消费者模型管道就是让不同的进程能够看到同一份资源且管道自带同步和互斥的机制。进程间通信的本质其实就是生产者消费者模型。 代码 blockQueue.hpp #pragma once #include iostream #include queue #include pthread.hconst int gcap 5;template class T class BlockQueue { public:BlockQueue(const int cap gcap) : _cap(cap){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_consumerCond, nullptr);pthread_cond_init(_productorCond, nullptr);}~BlockQueue(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_consumerCond);pthread_cond_destroy(_productorCond);}bool isFull(){return _cap _q.size();}void push(const T in){ // 生产pthread_mutex_lock(_mutex);while (isFull()) //细节1使用while 防止多线程被唤醒生产过多{ // 我们只能在临界区内部判断临界资源是否就绪 注定了我们在当前一定是持有锁的pthread_cond_wait(_productorCond, _mutex); // 如果队列为满生产者线程休眠 ,此时持有锁wait会将锁unlock// 当线程醒来的时候注定了继续从临界区内部继续运行因为是在临界区被切走的// 注定了当线程被唤醒的时候继续在pthread_cond_wait()函数继续向后运行又要重新申请锁申请成功才会彻底返回}// 没有满让他继续生产_q.push(in);//策略唤醒消费者线程pthread_cond_signal(_consumerCond);pthread_mutex_unlock(_mutex);}void pop(T *out){pthread_mutex_lock(_mutex);while (_q.empty()) //队列为空{pthread_cond_wait(_consumerCond, _mutex); }*out _q.front();_q.pop();//策略唤醒生产者pthread_cond_signal(_productorCond);pthread_mutex_unlock(_mutex);}private:std::queueT _q;int _cap;pthread_mutex_t _mutex;pthread_cond_t _consumerCond; // 消费者对应的条件变量 空 waitpthread_cond_t _productorCond; // 生产者对应的条件变量 满 wait }; task.hpp #pragma once#include iostream #include stringclass Task { public:Task() {}Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitCode(0){}void operator()(){switch (_op){case :_result _x _y;break;case -:_result _x - _y;break;case *:_result _x * _y;break;case /:if(_y0) _exitCode-1;else _result _x / _y;break; case %:if(_y0) _exitCode-1;else _result _x % _y;break;default:break;}}std::string formatArg(){return std::to_string(_x) _op std::to_string(_y) ;}std::string formatRes(){return std::to_string(_result) ( std::to_string(_exitCode));}~Task(){}private:int _x;int _y;char _op;int _result;int _exitCode; }; main.cc #include blockQueue.hpp #includetask.hpp #includectime #includeunistd.hvoid *consumer(void *args) {BlockQueueTask *bq static_castBlockQueueTask *(args);while(true){sleep(1);Task t;//1.将数据从blockqueue中获取 -- 获取到数据bq-pop(t);t();//2.结合某种业务逻辑处理数据std::coutconsumer data: t.formatArg()t.formatRes()std::endl;} }void *productor(void *args) {srand((uint64_t)time(nullptr)^getpid());BlockQueueTask *bq static_castBlockQueueTask *(args);std::string opers-*/%;while(true){//1.先通过某种渠道获取数据int xrand()%201;int yrand()%101;//2.将数据推送到blockqueue -- 完成生产过程char opopers[rand()%opers.size()];Task t(x,y,op);bq-push(t);std::coutproductor Task: t.formatArg()?std::endl;} }int main() {//BlockQueueint *bq new BlockQueueint();BlockQueueTask *bq new BlockQueueTask();// 单生产单消费 支持多生产多消费因为看到同一快资源使用同一把锁pthread_t c, p;pthread_create(c, nullptr, consumer, bq);pthread_create(p, nullptr, productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete bq;return 0; } 运行结果 2.信号量 概念 信号量本质就是一个计数器用来描述临界区中临界资源的数目大小。 临界资源如果可以被划分为更小的资源如果处理得当我们也有可能让多个线程同时访问临界资源从而实现并发。 但是每个线程想访问临界资源都得先申请信号量资源。 信号量操作接口 申请信号量成功时临界资源的数目会减一释放信号量时临界资源的数目会加一。 由于信号量是用来维护临界资源的首先必须得保证自身是安全的所以常规的对全局变量的或–操作肯定是不行的。 P操作(申请信号量) V操作(释放信号量) sem_init、sem_destroy初始化销毁信号量(具体用法与mutex和cond十分类似) #include semaphore.h int sem_init(sem_t *sem, int pshared, unsigned int value); // pshared: 默认为0 value信号量的初始值(count) int sem_destroy(sem_t *sem); // sem_t 信号量类型 // Link with -pthread. sem_wait、sem_signal: 申请、释放信号量 int sem_wait(sem_t *sem); // P操作 int sem_post(sem_t *sem); // V操作 // Link with -pthread. 基于环形队列的生产者消费者模型 但是现在的环形队列的判空判满不再使用中的两种方式判断因为有了信号量可以判定。 队列为空的时候消费者和生产者指向同一个位置。(生产和消费线程不能同时进行)(生产者执行) 队列为满的时候消费者和生产者也指向同一个位置。(生产和消费线程不能同时进行)(消费者执行) 当队列不为空不为满的时候消费者和生产者不指向同一个位置。(生产和消费线程可以并发执行) 根据上面三种情况基于环形队列的生产者消费者模型应该遵守以下规则 生产者不能把消费者套一个圈 消费者不能超过生产者 当指向同一个位置的时候要根据空、满状态判断让谁先执行 其他情况消费者和生产者可以并发执行 实现 ringQueue.hpp #pragma once#include iostream #include vector #include pthread.h #include semaphore.hstatic const int N 5;template class T class RingQueue { private:void P(sem_t s) {sem_wait(s);}void V(sem_t s){sem_post(s);}void Lock(pthread_mutex_t m){pthread_mutex_lock(m);}void Unlock(pthread_mutex_t m){pthread_mutex_unlock(m);}public:RingQueue(int num N) : _ring(num), _cap(num){sem_init(_data_sem, 0, 0);sem_init(_space_sem, 0, num);_c_step _p_step 0;pthread_mutex_init(_c_mutex, nullptr);pthread_mutex_init(_p_mutex, nullptr);}// 生产void push(const T in){// 1. 可以不用在临界区内部做判断就可以知道临界资源的使用情况// 2. 什么时候用锁对应的临界资源是否被整体使用P(_space_sem); // P() Lock(_p_mutex); _ring[_p_step] in;_p_step % _cap;Unlock(_p_mutex);V(_data_sem);}// 消费void pop(T *out){P(_data_sem);Lock(_c_mutex);*out _ring[_c_step];_c_step % _cap;Unlock(_c_mutex);V(_space_sem);}~RingQueue(){sem_destroy(_data_sem);sem_destroy(_space_sem);pthread_mutex_destroy(_c_mutex);pthread_mutex_destroy(_p_mutex);}private:std::vectorT _ring;int _cap; // 环形队列容器大小sem_t _data_sem; // 只有消费者关心sem_t _space_sem; // 只有生产者关心int _c_step; // 消费位置int _p_step; // 生产位置pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex; }; task.hpp #pragma once#include iostream #include stringclass Task { public:Task() {}Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitCode(0){}void operator()(){switch (_op){case :_result _x _y;break;case -:_result _x - _y;break;case *:_result _x * _y;break;case /:if(_y0) _exitCode-1;else _result _x / _y;break; case %:if(_y0) _exitCode-1;else _result _x % _y;break;default:break;}}std::string formatArg(){return std::to_string(_x) _op std::to_string(_y) ;}std::string formatRes(){return std::to_string(_result) ( std::to_string(_exitCode));}~Task(){}private:int _x;int _y;char _op;int _result;int _exitCode; }; main.cc #include ringQueue.hpp #include task.hpp #include ctime #include pthread.h #include memory #include sys/types.h #include unistd.h #include cstringusing namespace std;const char *ops -*/%;void *consumerRoutine(void *args) {RingQueueTask *rq static_castRingQueueTask *(args);while (true){Task t;rq-pop(t);t();cout consumer done, 处理完成的任务是 t.formatRes() endl;} }void *productorRoutine(void *args) {RingQueueTask *rq static_castRingQueueTask *(args);while (true){// sleep(1);int x rand() % 100;int y rand() % 100;char op ops[(x y) % strlen(ops)];Task t(x, y, op);rq-push(t);cout productor done, 生产的任务是: t.formatArg() endl;} }int main() {srand(time(nullptr) ^ getpid());RingQueueTask *rq new RingQueueTask();// 单生产单消费// pthread_t c, p;// pthread_create(c, nullptr, consumerRoutine, rq);// pthread_create(p, nullptr, productorRoutine, rq);// pthread_join(c, nullptr);// pthread_join(p, nullptr);//多生产多消费pthread_t c[3], p[2];for (int i 0; i 3; i)pthread_create(c i, nullptr, consumerRoutine, rq);for (int i 0; i 2; i)pthread_create(p i, nullptr, productorRoutine, rq);for (int i 0; i 3; i)pthread_join(c[i], nullptr);for (int i 0; i 2; i)pthread_join(p[i], nullptr);delete rq;return 0; } 运行结果 五、总结 互斥锁与信号量的异同 互斥锁由同一线程加放锁信号量可以由不同线程进行PV操作。 计数信号量允许多个线程且值为剩余可用资源数量。互斥锁保证多个线程对一个共享资源的互斥访问信号量用于协调多个线程对一系列资源的访问条。 条件变量与信号量的异同 使用条件变量可以一次唤醒所有等待者而这个信号量没有的功能。 信号量是有一个值而条件变量是没有的。从实现上来说一个信号量可以是用mutex count cond实现的。因为信号量有一个状态可以精准的同步信号量可以解决条件变量中存在的唤醒丢失问题。 条件变量一般需要配合互斥锁使用而信号量可根据情况而定。 有了互斥锁和条件变量还提供信号量的原因是尽管信号量的意图在于进程间同步互斥锁和条件变量的意图在于线程间同步但是信号量也可用于线程间互斥锁和条件变量也可用于进程间。信号量最有用的场景是用以指明可用资源的数量。
http://www.huolong8.cn/news/366468/

相关文章:

  • 建设网站的风险分析wordpress content widgets
  • 做网站全屏尺寸是多少钱网络推广营销策划方案
  • o2o网站建设特色大连网站建设招聘网
  • 网站建设的主要结构关于旅游网站建设的摘要
  • 个人制作个网站合肥知名网站建设公司
  • 慕课网站开发吉林省建设信息网官网
  • 网站需要哪些费用在线设计房屋效果图
  • 网站建设阿里云中国企业信息网查询系统官网
  • 网站设计应该遵循哪些原则完整网站开发视频
  • 广告公司网站(附falsh及源代码)长春建设厅网站首页
  • 养殖业网站模板中土集团北方建设有限公司网站
  • node 网站开发 视频教程网站建设开发公司报价
  • 福州网站开发私人网站页面设计怎么收费
  • 一个网站开发流程简洁网站模板素材
  • 网站设计素材网站推荐wordpress限定ip
  • 小型教育网站建设问题存在的门户网站导航建设方案
  • 响应式网站设计实训总结锦州北京网站建设
  • 外贸网站优化公司12345东莞网站
  • 网站制作好以后怎么管理免费h5游戏制作平台
  • 网站版权 备案icp万网企业邮箱
  • 山东省城乡住房建设厅网站中国都有哪些网站
  • 天长市城乡规划建设局网站php wordpress开发教程
  • 网站制作app免费软件成都环境建设网站
  • 哪里有做营销型网站的公司设计方案怎么写格式
  • 网站主色怎么选择外贸推广平台怎么做
  • 网站商城建设多少钱沈阳建设工程管理信息网
  • 网站后台密码修改音乐网站制作php
  • win7系统下动网站建设北京网站建设公司价格
  • 上海轨道交通建设查询网站建站程序asp
  • 怎么样开一个公司网站深圳产品设计公司排名前十强