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

html5移动端网站开发教程线上兼职的正规网站

html5移动端网站开发教程,线上兼职的正规网站,wap页面模板,网站开发程序开发文章目录 一、线程的概念1. 什么是线程Linux下并不存在真正的多线程#xff0c;而是用进程模拟的#xff01;Linux没有真正意义上的线程相关的系统调用#xff01;原生线程库pthread 2. 线程和进程的联系和区别3. 线程的优点4. 线程的缺点5. 线程异常6. 线程用途 二、二级页… 文章目录 一、线程的概念1. 什么是线程Linux下并不存在真正的多线程而是用进程模拟的Linux没有真正意义上的线程相关的系统调用原生线程库pthread 2. 线程和进程的联系和区别3. 线程的优点4. 线程的缺点5. 线程异常6. 线程用途 二、二级页表三、进程vs线程四、Linux线程控制1. POSIX线程库2. 线程创建让主线程创建一批新线程获取线程ID 3. 线程等待等待线程的函数叫做pthread_join 4. 线程终止5. 线程分离6. 线程ID的本质和进程地址空间布局 Linux多线程重点 1.了解线程概念理解线程与进程区别与联系。 2.学会线程控制线程创建线程终止线程等待。 3.了解线程分离与线程安全概念。 4.学会线程同步。 5.学会使用互斥量条件变量posix信号量以及读写锁。 6.理解基于读写锁的读者写者问题。 一、线程的概念 1. 什么是线程 课本观点线程是比进程更加轻量化的一种执行流 / 线程是在进程内部执行的一种执行流 我们的观点线程是CPU调度的基本单位 / 进程是承担系统资源的基本实体。 换言之当我们创建进程时是创建一个task_struct、创建地址空间、维护页表然后在物理内存当中开辟空间、构建映射打开进程默认打开的相关文件、注册信号对应的处理方案等等。 在一个程序里的一个执行路线就叫做线程thread。更准确的定义是线程是“一个进程内部的控制序列”。一切进程至少都有一个执行线程。线程在进程内部运行本质是在进程地址空间内运行。在Linux系统中在CPU眼中看到的PCB都要比传统的进程更轻量化从今天开始我们把pcb称为轻量级进程Light-weight process。透过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流。 在Linux看来描述线程的控制块和描述进程的控制块是类似的因此Linux并没有重新为线程设计数据结构而是直接复用了进程控制块所以我们说Linux中的所有执行流都叫做轻量级进程。 但也有支持真的线程的操作系统比如Windows操作系统因此Windows操作系统系统的实现逻辑一定比Linux操作系统的实现逻辑要复杂得多。 Linux下并不存在真正的多线程而是用进程模拟的 操作系统中存在大量的进程一个进程内又存在一个或多个线程因此线程的数量一定比进程的数量多当线程的数量足够多的时候很明显线程的执行粒度要比进程更细。 如果一款操作系统要支持真的线程那么就需要对这些线程进行管理。比如说创建线程、终止线程、调度线程、切换线程、给线程分配资源、释放资源以及回收资源等等所有的这一套相比较进程都需要另起炉灶搭建一套与进程平行的线程管理模块。 因此如果要支持真的线程一定会提高设计操作系统的复杂程度。在Linux看来描述线程的控制块和描述进程的控制块是类似的因此Linux并没有重新为线程设计数据结构而是直接复用了进程控制块所以我们说Linux中的所有执行流都叫做轻量级进程。 但也有支持真的线程的操作系统比如Windows操作系统因此Windows操作系统系统的实现逻辑一定比Linux操作系统的实现逻辑要复杂得多。 Linux没有真正意义上的线程相关的系统调用 既然在Linux中都没有真正意义上的线程了那么自然也没有真正意义上的线程相关的系统调用了。但是Linux可以提供创建轻量级进程的接口也就是创建进程共享空间其中最典型的代表就是vfork函数。 pid_t vfork(void);vfork函数的返回值与fork函数的返回值相同 给父进程返回子进程的PID。给子进程返回0。 vfork的主要作用是为了在创建新进程时减少资源的开销尤其是在子进程很快就会调用exec的情况下。它在性能上优于fork但由于其特殊的语义要小心使用以避免潜在的问题。在vfork中父进程会被阻塞直到子进程调用exec或者exit。这是因为子进程共享父进程的地址空间如果父进程在子进程修改了这个地址空间之前继续执行可能导致未定义的行为。 只不过vfork函数创建出来的子进程与其父进程共享地址空间例如在下面的代码中父进程使用vfork函数创建子进程子进程将全局变量g_val由100改为了200父进程休眠3秒后再读取到全局变量g_val的值。 #include stdio.h #include stdlib.h #include sys/types.h #include unistd.h int g_val 100; int main() {pid_t id vfork();if (id 0){//childg_val 200;printf(child:PID:%d, PPID:%d, g_val:%d\n, getpid(), getppid(), g_val);exit(0);}//fathersleep(3);printf(father:PID:%d, PPID:%d, g_val:%d\n, getpid(), getppid(), g_val);return 0; }原生线程库pthread 在Linux中站在内核角度没有真正意义上线程相关的接口但是站在用户角度当用户想创建一个线程时更期望使用thread_create这样类似的接口而不是vfork函数因此系统为用户层提供了原生线程库pthread。 原生线程库实际就是对轻量级进程的系统调用进行了封装在用户层模拟实现了一套线程相关的接口。 因此对于我们来讲在Linux下学习线程实际上就是学习在用户层模拟实现的这一套接口而并非操作系统的接口。 pthread库并不是基于vfork来创建线程的。pthread库通常使用底层的系统调用如clone。 2. 线程和进程的联系和区别 线程的特点 线程创建更简单线程是在进程内部执行的本质是线程在进程的地址空间中运行Linux下的线程是用进程的pcb模拟的所以Linux下的线程是轻量级进程。OS如果要支持线程也像进程一样必须先管理进程先描述再组织TCB T是thread 进程的特点 进程 内核数据结构 代码和数据 以前我们讲的进程内部只有一个执行流的进程 今天我们讲的进程内部可以有多个执行流的进程 3. 线程的优点 创建一个新线程的代价要比创建一个新进程小得多。与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多。线程占用的资源要比进程少很多。能充分利用多处理器的可并行数量。在等待慢速IO操作结束的同时程序可执行其他的计算任务。计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现。IO密集型应用为了提高性能将IO操作重叠线程可以同时等待不同的IO操作。 [!info] 为什么线程切换效率高 要换的寄存器少不需要重新更新cache 4. 线程的缺点 性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。健壮性降低 编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。缺乏访问控制 进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响。编程难度提高 编写与调试一个多线程程序比单线程程序困难得多。 5. 线程异常 单个线程如果出现除零、野指针等问题导致线程崩溃进程也会随着崩溃。线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出。 6. 线程用途 合理的使用多线程能提高CPU密集型程序的执行效率。合理的使用多线程能提高IO密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是多线程运行的一种表现。 二、二级页表 在Linux下虚拟内存管理使用页表来实现虚拟地址到物理地址的映射。对于32位的机器虚拟地址空间是4GB232即这张表一共有232个映射表项 每一个表项中除了要有虚拟地址和与其映射的物理地址以外实际还需要有一些权限相关的信息比如我们所说的用户级页表和内核级页表实际就是通过权限进行区分的 每个应表项中存储一个物理地址和一个虚拟地址就需要8个字节考虑到还需要包含权限相关的各种信息这里每一个表项就按10个字节计算。 这里一共有232个表项也就意味着存储这张页表我们需要用232 * 10个字节也就是40GB。而在32位平台下我们的内存可能一共就只有4GB也就是说我们根本无法存储这样的一张页表。 因此所谓的页表并不是单纯的一张表 在32位Linux系统中最常见的是使用两级页表也称为二级页表即页目录表和页表 这实际上就是我们所谓的二级页表其中页目录项是一级页表页表项是二级页表。 32位系统下因为地址空间大小限制虚拟地址空间大小为4GB。即使使用最小的页面大小通常是4KB也需要232个页面来覆盖整个地址空间。如果使用单层页表那么需要232个页表项来进行映射每个页表项占用4字节32位系统因此单个页表需要大约2^32 * 10个字节的空间。而这只是一个页表如果是单层映射需要存储整个地址空间的映射这将需要非常大的空间这在32位系统下是不切实际的。 因此为了在32位Linux系统中实现有效的内存管理需要使用二级页表来平衡地址空间大小的限制和内存管理的效率要求。划分页表的本质是划分进程地址空间 三、进程vs线程 进程是承担分配系统资源的基本实体线程是CPU调度的基本单位。 线程与进程共享的内容 因为是在同一个地址空间因此所谓的代码段Text Segment、数据段Data Segment都是共享的 如果定义一个函数在各线程中都可以调用。如果定义一个全局变量在各线程中都可以访问到。 进程资源和环境 文件描述符表。进程打开一个文件后其他线程也能够看到每种信号的处理方式。SIG_IGN、SIG_DFL或者自定义的信号处理函数当前工作目录。cwd用户ID和组ID。 每个线程独有的数据 线程ID。一组寄存器。存储每个线程的上下文信息栈。每个线程都有临时的数据需要压栈出栈errno。C语言提供的全局变量每个线程都有自己的信号屏蔽字。调度优先级。 四、Linux线程控制 1. POSIX线程库 pthreadPOSIX Threads是一种跨平台的线程库标准。POSIX Threads定义了一套线程API规范可以在多个操作系统上使用包括Linux。在Linux系统中pthread库是一种实现这个规范的库用于创建和管理线程。因此pthread常常被称为Linux下的原生线程库指的是它是Linux上支持POSIX线程规范的一种库。 pthread线程库是应用层的原生线程库 应用层指的是这个线程库并不是系统接口直接提供的而是由第三方帮我们提供的。“原生”指的是大部分Linux系统都会默认带上该线程库。与线程有关的函数构成了一个完整的系列绝大多数函数的名字都是以“pthread_”打头的。要使用这些函数库要通过引入头文件pthreaad.h。链接这些线程函数库时要使用编译器命令的“-lpthread”选项。 #mermaid-svg-iKOrDngAZGJCgmkw {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-iKOrDngAZGJCgmkw .error-icon{fill:#552222;}#mermaid-svg-iKOrDngAZGJCgmkw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iKOrDngAZGJCgmkw .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-iKOrDngAZGJCgmkw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iKOrDngAZGJCgmkw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iKOrDngAZGJCgmkw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iKOrDngAZGJCgmkw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iKOrDngAZGJCgmkw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iKOrDngAZGJCgmkw .marker.cross{stroke:#333333;}#mermaid-svg-iKOrDngAZGJCgmkw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iKOrDngAZGJCgmkw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iKOrDngAZGJCgmkw .cluster-label text{fill:#333;}#mermaid-svg-iKOrDngAZGJCgmkw .cluster-label span{color:#333;}#mermaid-svg-iKOrDngAZGJCgmkw .label text,#mermaid-svg-iKOrDngAZGJCgmkw span{fill:#333;color:#333;}#mermaid-svg-iKOrDngAZGJCgmkw .node rect,#mermaid-svg-iKOrDngAZGJCgmkw .node circle,#mermaid-svg-iKOrDngAZGJCgmkw .node ellipse,#mermaid-svg-iKOrDngAZGJCgmkw .node polygon,#mermaid-svg-iKOrDngAZGJCgmkw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iKOrDngAZGJCgmkw .node .label{text-align:center;}#mermaid-svg-iKOrDngAZGJCgmkw .node.clickable{cursor:pointer;}#mermaid-svg-iKOrDngAZGJCgmkw .arrowheadPath{fill:#333333;}#mermaid-svg-iKOrDngAZGJCgmkw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iKOrDngAZGJCgmkw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iKOrDngAZGJCgmkw .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-iKOrDngAZGJCgmkw .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-iKOrDngAZGJCgmkw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iKOrDngAZGJCgmkw .cluster text{fill:#333;}#mermaid-svg-iKOrDngAZGJCgmkw .cluster span{color:#333;}#mermaid-svg-iKOrDngAZGJCgmkw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iKOrDngAZGJCgmkw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 系统调用接口 用户和用户操作接口 Linux提供的轻量级进程 - LWP 用户 pthread线程库 错误检查 传统的一些函数是成功返回0失败返回-1并且对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno而大部分POSIX函数会这样做而是将错误代码通过返回值返回。pthreads同样也提供了线程内的errno变量以支持其他使用errno的代码。对于pthreads函数的错误建议通过返回值来判定因为读取返回值要比读取线程内的errno变量的开销更小。 2. 线程创建 创建线程的函数叫做pthread_create pthread_create - create a new thread SYNOPSIS#include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);Compile and link with -pthread.参数说明 thread获取创建成功的线程ID该参数是一个输出型参数。attr用于设置创建线程的属性传入nullptr表示使用默认属性。start_routine该参数是一个函数地址表示线程例程即线程启动后要执行的函数。arg传给线程例程的参数。 线程创建成功返回0失败返回错误码。 使用ps -aL命令可以显示当前的轻量级进程。 默认情况下不带-L看到的就是一个个的进程。带-L就可以查看到每个进程内的多个轻量级进程。 while :; do ps -aL; sleep 1; done让主线程创建一批新线程 #include iostream #include unistd.h #include pthread.h #include functional #include vectorusing namespace std;using func_t std::functionvoid();const int threadnum 5;class ThreadData { public:ThreadData(const std::string name, const uint64_t ctime, func_t f):threadname(name), createtime(ctime), func(f){}public:std::string threadname;uint64_t createtime;func_t func; };void Print() {std::cout 我是线程执行的大任务的一部分 std::endl; }// 新线程 void* ThreadRoutine(void* args) {int a 10;ThreadData* td static_castThreadData*(args);while (true){std::cout new thread thread name: td-threadname create time: td-createtime std::endl;td-func();// if(td-threadname thread-4)// {// std::cout td-threadname 触发了异常 std::endl;// a / 0; // 故意制造异常// }sleep(1);} }int main() {std::vectorpthread_t pthreads;for (size_t i 0; i threadnum; i){char threadname[64];snprintf(threadname, sizeof(threadname), %s-%d, thread, i 1);pthread_t tid; // 线程id typedef unsigned long int pthread_t;ThreadData* td new ThreadData(threadname, (uint64_t)time(nullptr), Print);pthread_create(tid, nullptr, ThreadRoutine, td);pthreads.push_back(tid);sleep(1);}std::cout thread id: ;for (const auto tid : pthreads){std::cout tid ,;}std::cout std::endl;while (true){std::cout main thread std::endl;sleep(3);}return 0; }LWPLight Weight Process就是轻量级进程的ID可以看到显示的两个轻量级进程的PID是相同的因为它们属于同一个进程 注意 用pthread_self函数获得的线程ID与内核的LWP的值是不相等的pthread_self函数获得的是用户级原生线程库的线程ID而LWP是内核的轻量级进程ID它们之间是一对一的关系。 获取线程ID 常见获取线程ID的方式有两种 创建线程时通过输出型参数获得。通过调用pthread_self函数获得。pthread_t pthread_self(void);示例 using namespace std;const int threadnum 5;// 新线程 void* ThreadRoutine(void* args) {std::cout 我是新线程通过pthread_self获得的线程id是 pthread_self() std::endl; }int main() {std::vectorpthread_t pthreads;for (size_t i 0; i threadnum; i){pthread_t tid; // 线程id typedef unsigned long int pthread_t;pthread_create(tid, nullptr, ThreadRoutine, nullptr);pthreads.push_back(tid);sleep(1);}std::cout 直接打印pthread_t线程id std::endl;for (const auto tid : pthreads){std::cout tid std::endl;}// 线程等待for (const auto tid : pthreads){pthread_join(tid, nullptr);}return 0; }线程ID与内核的LWP的值是不相等的 pthread_self函数获得的是用户级的POSIX程库的线程IDLWP是内核的轻量级进程ID它们之间是一对一的关系 3. 线程等待 等待线程的函数叫做pthread_join int pthread_join(pthread_t thread, void **retval);thread被等待线程的ID。retval线程退出时的退出码信息这是一个输出型参数 总结如下 如果thread线程通过return返回retval所指向的是thread线程函数的返回值。 如果thread线程被别的线程调用pthread_cancel异常终止掉retval所指向的单元里存放的是常数PTHREAD_CANCELED用grep命令查询它 grep -ER PTHREAD_CANCELED /usr/include/如果thread线程是自己调用pthread_exit终止的retval所指向的单元存放的是传给pthread_exit的参数。 如果对thread线程的终止状态不感兴趣可以传nullptr给retval参数。 [!Question] 为什么线程退出时只能拿到线程的退出码 如果我们等待的是一个进程那么当这个进程退出时我们可以通过wait函数或是waitpid函数的输出型参数status获取到退出进程的退出码、退出信号以及core dump标志。 那为什么等待线程时我们只能拿到退出线程的退出码难道线程不会出现异常吗 线程在运行过程中当然也会出现异常线程和进程一样线程退出的情况也有三种 代码运行完毕结果正确。代码运行完毕结果不正确。代码异常终止。 因此我们也需要考虑线程异常终止的情况但是pthread_join函数无法获取到线程异常退出时的信息。因为线程是进程内的一个执行分支如果进程中的某个线程崩溃了那么整个进程也会因此而崩溃此时我们根本没办法执行pthread_join函数因为整个进程已经退出了。 例如我们在线程的执行例程当中制造一个除零错误当某一个线程执行到此处时就会崩溃进而导致整个进程崩溃。 // 新线程 void* ThreadRoutine(void* args) {int a 10;ThreadData* td static_castThreadData*(args);while (true){std::cout new thread thread name: td-threadname create time: td-createtime std::endl;td-func();if(td-threadname thread-4){std::cout td-threadname 触发了异常 std::endl;a / 0; // 故意制造异常}sleep(1);} }4. 线程终止 如果需要只终止某个线程而不是终止整个进程可以有三种方法 从线程函数return。线程可以自己调用pthread_exit函数终止自己。一个线程可以调用pthread_cancel函数终止同一进程中的另一个线程。 5. 线程分离 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成内存泄漏。但如果我们不关心线程的返回值join也是一种负担此时我们可以将该线程进行分离后续当线程退出时就会自动释放线程资源。一个线程如果被分离了这个线程依旧要使用该进程的资源依旧在该进程内运行甚至这个线程崩溃了一定会影响其他线程只不过这个线程退出时不再需要主线程去join了当这个线程退出时系统会自动回收该线程所对应的资源。可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离。joinable和分离是冲突的一个线程不能既是joinable又是分离的。 分离线程的函数叫做pthread_detach int pthread_detach(pthread_t thread);参数说明 thread被分离线程的ID。 返回值说明 线程分离成功返回0失败返回错误码。 #include stdio.h #include stdlib.h #include pthread.h #include unistd.h #include sys/types.hvoid* Routine(void* arg) {pthread_detach(pthread_self());char* msg (char*)arg;int count 0;while (count 5){printf(I am %s...pid: %d, ppid: %d, tid: %lu\n, msg, getpid(), getppid(), pthread_self());sleep(1);count;}pthread_exit((void*)6666); } int main() {pthread_t tid[5];for (int i 0; i 5; i){char* buffer (char*)malloc(64);sprintf(buffer, thread %d, i);pthread_create(tid[i], NULL, Routine, buffer);printf(%s tid is %lu\n, buffer, tid[i]);}while (1){printf(I am main thread...pid: %d, ppid: %d, tid: %lu\n, getpid(), getppid(), pthread_self());sleep(1);}return 0; } 6. 线程ID的本质和进程地址空间布局 pthread_create函数会产生一个线程ID存放在第一个参数指向的地址中该线程ID和内核中的LWP不是一回事。内核中的LWP属于进程调度的范畴因为线程是轻量级进程是操作系统调度器的最小单位所以需要一个数值来唯一表示该线程。pthread_create函数第一个参数指向一个虚拟内存单元该内存单元的地址即为新创建线程的线程ID这个ID属于NPTL线程库的范畴线程库的后续操作就是根据该线程ID来操作线程的。线程库NPTL提供的pthread_self函数获取的线程ID和pthread_create函数第一个参数获取的线程ID是一样的。 pthread_t到底是什么类型呢 首先Linux不提供真正的线程只提供LWP也就意味着操作系统只需要对内核执行流LWP进行管理而供用户使用的线程接口等其他数据应该由线程库自己来管理因此管理线程时的“先描述再组织”就应该在线程库里进行。 通过ldd命令可以看到我们采用的线程库实际上是一个动态库。 进程运行时动态库被加载到内存然后通过页表映射到进程地址空间中的共享区此时该进程内的所有线程都是能看到这个动态库的。 我们说每个线程都有自己私有的栈其中主线程采用的栈是进程地址空间中原生的栈而其余线程采用的栈就是在共享区中开辟的。除此之外每个线程都有自己的struct pthread当中包含了对应线程的各种属性每个线程还有自己的线程局部存储当中包含了对应线程被切换时的上下文数据。 每一个新线程在共享区都有这样一块区域对其进行描述因此我们要找到一个用户级线程只需要找到该线程内存块的起始地址然后就可以获取到该线程的各种信息。 上面我们所用的各种线程函数本质都是在库内部对线程属性进行的各种操作最后将要执行的代码交给对应的内核级LWP去执行就行了也就是说线程数据的管理本质是在共享区的。 pthread_t到底是什么类型取决于实现但是对于Linux目前实现的NPTL线程库来说线程ID本质就是进程地址空间共享区上的一个虚拟地址同一个进程中所有的虚拟地址都是不同的因此可以用它来唯一区分每一个线程。 例如我们也可以尝试按地址的形式对获取到的线程ID进行打印。
http://www.lakalapos1.cn/news/75217/

相关文章:

  • 合肥seo建站高清电影下载
  • 浙江建设厅官方网站seo同行网站
  • 网站备案域名更改公司客户细分精准营销
  • 个人网站如何制作教程郑州网络推广电话
  • wordpress仿站插件企业做网站需要提供什么资料
  • 网站开发公司安心加盟wordpress页脚怎么修改
  • 游戏模型外包网站衍艺网站建设
  • wordpress销售插件当阳seo外包
  • 网站建设工作分解深圳福田 外贸网站建设
  • 网站建设需求模板做网站的图片取材
  • 电商优惠券网站 建设福鼎网站建设培训
  • wordpress外贸网站建设用服务器ip可以做网站吗
  • 双流网站建设做rom的网站
  • 响应式网站费用如何给网站做404页面
  • 不备案 网站 盈利团购网站模块
  • 怎么做flash网站伏羲方舟网站建设
  • 南昌网络营销网站滨州论坛网站建设
  • 快速设计一个网站网站做收款要什么条件
  • 企业网站开发创意云南创网科技有限公司
  • 网站建设图片手机创建网站用突唯阿做响应式网站
  • 自考都到哪个网站找题做it培训机构好
  • 网站开发进度设计北京一诺互联科技有限公司
  • 中粮我买网是哪个公司做的网站营销推广型网站价格
  • o2o家电维修网站开发深圳网站建设的费用
  • 个人网站域名注册设计一套app页面多少钱
  • 网站开发软件教程清华大学精品课程网站
  • 网站开发项目需要什么人员有哪个网站可以做链接
  • 知企业网站怎么打不开备案信息 网站名
  • 网站seo优化建议智慧团建学生登录入口手机版
  • 做微商如何网站推广店铺设计图片