中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

鄭州hi寶貝網(wǎng)站建設(shè)公司定制網(wǎng)站開發(fā)公司

鄭州hi寶貝網(wǎng)站建設(shè)公司,定制網(wǎng)站開發(fā)公司,四川省紀(jì)委網(wǎng)站建設(shè),西安旅游攻略3日游文章目錄一、進(jìn)程控制1.1 POSIX線程庫1.2 創(chuàng)建線程pthread_create1.2.1 創(chuàng)建一批線程1.3 終止線程pthread_exit1.4 線程等待pthread_jion1.4.1 線程的返回值(退出碼)1.5 取消線程pthread_cancel1.6 C多線程1.7 分離線程pthread_detach二、線程ID值三、線…

文章目錄

  • 一、進(jìn)程控制
    • 1.1 POSIX線程庫
    • 1.2 創(chuàng)建線程pthread_create
      • 1.2.1 創(chuàng)建一批線程
    • 1.3 終止線程pthread_exit
    • 1.4 線程等待pthread_jion
      • 1.4.1 線程的返回值(退出碼)
    • 1.5 取消線程pthread_cancel
    • 1.6 C++多線程
    • 1.7 分離線程pthread_detach
  • 二、線程ID值
  • 三、線程局部存儲__thread
  • 四、原生線程庫的封裝

一、進(jìn)程控制

1.1 POSIX線程庫

這是由原生線程庫提供的,遵守POSIX標(biāo)準(zhǔn)。這個標(biāo)準(zhǔn)就像前面學(xué)過的system V標(biāo)準(zhǔn)。
有以下特點(diǎn):

1?? 與線程有關(guān)的函數(shù)構(gòu)成了一個完整的系列,絕大多數(shù)函數(shù)的名字都是以pthread_打頭的。
2?? 要使用這些函數(shù)庫,要通過引入頭文<pthread.h>。
3?? 鏈接這些線程函數(shù)庫時要使用編譯器命令的-lpthread選項(xiàng)。

1.2 創(chuàng)建線程pthread_create

#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.// 鏈接的時候必須加上-lpthreadRETURN VALUE
On success, pthread_create() returns 0; 
on error, it returns an error number, and the contents of *thread are undefined.

參數(shù)說明:
thread:線程id
attr:線程屬性,直接設(shè)為null
start_routine:函數(shù)指針
arg:這個參數(shù)會傳遞進(jìn)start_routinevoid*參數(shù)中。

這里在鏈接的時候要注意link到系統(tǒng)給的原生線程庫-lpthread。

對于錯誤的檢查:
像我們以前的函數(shù)基本都是設(shè)置進(jìn)全局變量errno來指示錯誤,而pthreads函數(shù)出錯時不會設(shè)置全局變量errno,因?yàn)槿孔兞繒欢鄠€線程共享。它會將錯誤代碼通過返回值返回

1.2.1 創(chuàng)建一批線程

#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <unistd.h>using std::cout;
using std::endl;void* start_stream(void* str)
{std::string name = static_cast<const char*>(str);while(true){cout << "i am new thread: " << name << endl;sleep(1);}
}#define NUM 10int main()
{// 創(chuàng)建一批線程for(int i = 0; i < NUM; i++){pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"new thread");}// 創(chuàng)建完畢while(true){cout << "-----------create success-----------" << endl;sleep(1);}return 0;
}

在這里插入圖片描述
在這里插入圖片描述
現(xiàn)在我們給每個線程傳遞進(jìn)去的時候加上一個編號:

for(int i = 1; i <= NUM; i++)
{pthread_t tid;char buf[64];snprintf(buf, sizeof(buf), "%s:%d", "thread", i);pthread_create(&tid, nullptr, start_stream, (void*)buf);
}

在這里插入圖片描述
為什么會出現(xiàn)這個現(xiàn)象呢?
在這里插入圖片描述
那么真正的寫法應(yīng)該是什么呢?
我們主要目的是讓每個線程都不會共享buf。

class ThreadData
{
public:pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);while(true){cout << "i am new thread: " << ptd->_buf << endl;sleep(1);}
}#define NUM 10int main()
{// 創(chuàng)建一批線程for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);}// 創(chuàng)建完畢while(true){cout << "-----------create success-----------" << endl;sleep(1);}return 0;
}

這樣每個線程所獲得的buf都是不同的。
在這里插入圖片描述
再加上一個計數(shù)器:

void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 10;while(cnt){// cout << "i am new thread: " << ptd->_buf << " cnt: " << cnt << endl;printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}delete ptd;return nullptr;
}

在這里插入圖片描述
這里可以看到每個線程cnt的地址都不一樣,所以每個線程都有不同的cnt,不會相互影響。
這就證明了每一個線程都有自己獨(dú)立的棧結(jié)構(gòu)。
這個調(diào)用方法函數(shù)就是一個可重入函數(shù),每個線程都會形成獨(dú)立的棧幀。

1.3 終止線程pthread_exit

首先要知道最基本的終止方法:
在這里插入圖片描述
這里要注意exit不能用來終止線程,使用exit整個進(jìn)程都會退出,所以exit是用來終止進(jìn)程的。

POSIX線程庫專門給了一個接口用來結(jié)束終止線程。

#include <pthread.h>void pthread_exit(void *retval);Compile and link with -pthread.

參數(shù)可以默認(rèn)設(shè)置為nullptr。

void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 10;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}delete ptd;//return nullptr;pthread_exit(nullptr);// 結(jié)束線程
}

在這里插入圖片描述
可以看到把新建線程結(jié)束后,主線程還在繼續(xù)運(yùn)行。
當(dāng)然也可以使用return來終止。

1.4 線程等待pthread_jion

跟進(jìn)程一樣線程也是要等待的,如果不等待,就會造成內(nèi)存泄漏(類似僵尸進(jìn)程)。
而等待主要是要做:

  • 獲取新線程的退出信息。
  • 回收新線程對應(yīng)的PCB等內(nèi)核資源,防止內(nèi)存泄漏。
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);Compile and link with -pthread.RETURN VALUE
On success, pthread_join() returns 0; 
on error, it returns an error number.

參數(shù)說明:

thread:要等待的線程。
retval:后面1.4.1線程的返回值詳細(xì)說明。

class ThreadData
{
public:pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 10;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}delete ptd;// return nullptr;pthread_exit(nullptr);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}return 0;
}

這是沒有等待線程的代碼
在這里插入圖片描述
可以看到主線程運(yùn)行完就直接結(jié)束了進(jìn)程,所有新線程也全部結(jié)束。

進(jìn)行線程等待的代碼:

class ThreadData
{
public:pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 5;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}// return nullptr;pthread_exit(nullptr);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}// 線程等待for(auto& e : tids){int n = pthread_join(e->_tid, nullptr);assert(n == 0);delete e;}cout << "main thread quit" << endl;return 0;
}

在這里插入圖片描述
可以看到新線程也都執(zhí)行到了結(jié)尾。

1.4.1 線程的返回值(退出碼)

在這里插入圖片描述
這里的返回值究竟是干什么的呢?
在這里插入圖片描述

這樣我們就可以在pthread_jion的時候來獲取線程結(jié)束的返回值。

class ThreadData
{
public:int _number;pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 5;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}// return nullptr;pthread_exit((void*)ptd->_number);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();ptd->_number = i;snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}// 線程等待for(auto& e : tids){void* ret = nullptr;int n = pthread_join(e->_tid, &ret);assert(n == 0);printf("join success: %d\n", (long long)(ret));delete e;}cout << "main thread quit" << endl;return 0;
}

在這里插入圖片描述
可以看到拿到了返回值(退出碼)。

那么這個ret是怎么拿到數(shù)據(jù)的呢?

線程結(jié)束時會把返回值寫入pthread庫暫時保存,這時候其實(shí)我們設(shè)置的ret變量類型和庫中臨時保存的類型相同,但是由于這是個函數(shù)調(diào)用,我們沒辦法直接賦值
在這里插入圖片描述
所以我們吧ret的地址傳進(jìn)去,函數(shù)內(nèi)部自己幫我們實(shí)現(xiàn)(藍(lán)色)
在這里插入圖片描述

這里我們思考一個問題:線程退出的時候?yàn)槭裁礇]有跟進(jìn)程退出那樣有退出信號?

因?yàn)樾盘柺前l(fā)給進(jìn)程的,整個進(jìn)程都會被退出,要退出信號也沒有意義了。
所以pthread_jion默認(rèn)認(rèn)為能夠調(diào)用成功,不考慮異常問題,異常應(yīng)該是進(jìn)程考慮的問題。

1.5 取消線程pthread_cancel

想要取消線程的前提是線程得先跑起來。

#include <pthread.h>int pthread_cancel(pthread_t thread);Compile and link with -pthread.RETURN VALUE
On success, pthread_cancel() returns 0; 
on error, it returns a nonzero error number.

我們可以取消一半的線程,觀察這一半和剩余一半的區(qū)別:

class ThreadData
{
public:int _number;pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 5;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}// return nullptr;pthread_exit((void*)100);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();ptd->_number = i;snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}// 取消一半的線程for(int i = 0; i < tids.size() / 2; i++){pthread_cancel(tids[i]->_tid);}// 線程等待for(auto& e : tids){void* ret = nullptr;int n = pthread_join(e->_tid, &ret);assert(n == 0);printf("join success: %d\n", (long long)(ret));delete e;}cout << "main thread quit" << endl;return 0;
}

在這里插入圖片描述

在這里插入圖片描述
所以線程如果是被取消的,那么它的退出碼是-1。它其實(shí)是一個宏:PTHREAD_CANCELED

1.6 C++多線程

我們可以用C++給我們提供的線程庫:

#include <iostream>
#include <thread>
#include <unistd.h>using std::cout;
using std::endl;int main()
{std::thread t([](){while(true){cout << "i am new thread" << endl;sleep(1);};});while(true){cout << "i am main thread" << endl;sleep(1);}t.join();return 0;
}

在這里插入圖片描述
注意編譯的時候得鏈接-lpthread。
因?yàn)槿魏握Z言想要在linux中實(shí)現(xiàn)多線程,必須要使用pthread庫。而我們現(xiàn)在寫的C++11中的多線程,本質(zhì)上就是對pthread庫的封裝

在后邊會模擬線程庫的實(shí)現(xiàn)。

1.7 分離線程pthread_detach

默認(rèn)情況下,新創(chuàng)建的線程是joinable的,線程退出后,需要對其進(jìn)行pthread_join操作,否則無法釋放資源,從而造成系統(tǒng)泄漏。
如果不關(guān)心線程的返回值,join是一種負(fù)擔(dān),這個時候,我們可以告訴系統(tǒng),當(dāng)線程退出時,自動釋放線程資源。

先寫一個正常的創(chuàng)建線程:

#include <iostream>
#include <string>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using std::cout;
using std::endl;std::string GetId(const pthread_t& _id)
{char buf[64];snprintf(buf, sizeof(buf), " 0x%x", _id);return buf;
}void* start_stream(void* args)
{std::string name = static_cast<const char*>(args);while(true){cout << "i am new thread name: " << name << GetId(pthread_self()) << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"thread one");cout << "i am main thread, new id: " << GetId(tid) << endl;pthread_join(tid, nullptr);return 0;
}

在這里插入圖片描述

解釋一下這里的pthread_self(),哪個線程調(diào)用這個函數(shù),就可以獲得該線程的id。

#include <pthread.h>pthread_t pthread_self(void);Compile and link with -pthread.RETURN VALUE
This function always succeeds, returning the calling thread's ID.

而上面我們進(jìn)過驗(yàn)證看到獲取的確實(shí)是新線程的id。

有了獲取線程id的方法接下來就可以進(jìn)行線程的分離。

#include <pthread.h>int pthread_detach(pthread_t thread);Compile and link with -pthread.RETURN VALUE
On success, pthread_detach() returns 0; 
on error, it returns an error number.

因?yàn)槿绻粋€線程設(shè)置了分離,那么這個線程就不能被等待,而如果等待失敗,就會返回錯誤碼:

std::string GetId(const pthread_t& _id)
{char buf[64];snprintf(buf, sizeof(buf), " 0x%x", _id);return buf;
}void* start_stream(void* args)
{std::string name = static_cast<const char*>(args);pthread_detach(pthread_self());// 線程分離int cnt = 5;while(cnt--){cout << "i am new thread name: " << name << GetId(pthread_self()) << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"thread one");cout << "i am main thread, new id: " << GetId(tid) << endl;// 一個線程設(shè)置分離狀態(tài)就不能被等待了int n = pthread_join(tid, nullptr);cout << "error: " << n << "->" << strerror(n) << endl;return 0;
}

在這里插入圖片描述
但是這里的結(jié)果顯示是正確的。是什么原因呢?

因?yàn)橹骶€程和新線程執(zhí)行順序不確定,新線程還沒來得及分離,主線程就執(zhí)行到j(luò)ion了,主線程就開始阻塞等待了,后續(xù)新線程分離了也不知道。

所以我們讓主線程慢一點(diǎn)執(zhí)行或者讓主線程進(jìn)行分離:
在這里插入圖片描述
在這里插入圖片描述
所以一旦有一個線程被分離,那么我們就可以不用管這個線程了,做自己的事情即可

二、線程ID值

我們可以看到我們打印出來的線程ID:
在這里插入圖片描述
那么它究竟是什么呢?

我們知道linux創(chuàng)建進(jìn)程要通過pthread庫提供的接口。所以原生庫中可能存在多個線程,那么我們就必須要把這些線程管理起來。而描述這些線程的結(jié)構(gòu)體是由原生線程庫實(shí)現(xiàn)的,每一個結(jié)構(gòu)體對應(yīng)一個輕量級進(jìn)程,所以線程是由庫和OS共同實(shí)現(xiàn)的。

在這里插入圖片描述
而庫中描述線程的結(jié)構(gòu)體到底是什么樣子的呢?
在這里插入圖片描述
每個結(jié)構(gòu)體在庫中就像一個數(shù)組一樣被存儲起來。而每個結(jié)構(gòu)體的起始位置的地址就是線程的ID值
這里也可以看到每個線程都有自己獨(dú)立的棧結(jié)構(gòu),由庫來維護(hù)。

主線程也有自己獨(dú)立的棧:
在這里插入圖片描述

三、線程局部存儲__thread

int g_val = 0;void* start_stream(void* args)
{std::string name = static_cast<const char *>(args);while(true){cout << name;printf(" g_val: %d &g_val 0x%x\n", g_val, &g_val);sleep(1);++g_val;}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"thread one");while(true){printf("main g_val: %d &g_val 0x%x\n", g_val, &g_val);sleep(1);}pthread_join(tid, nullptr);return 0;
}

在這里插入圖片描述
可以看到不同的線程共享一個全局變量

但是當(dāng)我們給g_val添加__thread屬性后:
在這里插入圖片描述

在這里插入圖片描述
從結(jié)果看g_val不是共享的了,而是每個線程獨(dú)有一個。
添加__thread,可以將一個內(nèi)置類型設(shè)置為線程局部存儲。給每個線程都來一份。
是介于全局變量和局部變量之間線程特有的屬性

四、原生線程庫的封裝

我們想像1.6C++多線程那樣來使用多線程該怎么辦呢?
當(dāng)然是對原生線程庫進(jìn)行封裝。

// mythread.hpp
#pragma once#include <iostream>
#include <pthread.h>
#include <cstring>
#include <string>
#include <cassert>
#include <functional>class Thread;class Context
{
public:Thread *_this;void *_args;Context(): _this(nullptr), _args(nullptr){}
};class Thread
{
public:typedef std::function<void*(void*)> func_t;Thread(func_t fun, void* args, int number): _func(fun), _args(args){char buf[64];snprintf(buf, sizeof buf, "thread-%d", number);_name = buf;}// 不加static就會有this指針static void* start_routine(void* args){//return _func(args);// 無this指針,無法調(diào)用Context* pct = static_cast<Context*>(args);pct->_this->_func(pct->_args);delete pct;return nullptr;}void start(){// int n = pthread_create(&_tid, nullptr, _func, _args);// _func是C++函數(shù),pthread_create是C接口,不能混編Context* pct = new Context();pct->_this = this;pct->_args = _args;int n = pthread_create(&_tid, nullptr, start_routine, pct);assert(n == 0);(void)n;}void join(){int n = pthread_join(_tid, nullptr);assert(n == 0);(void)n;}
private:std::string _name;// 線程名pthread_t _tid;// 線程idfunc_t _func;// 調(diào)用方法void *_args;// 參數(shù)
};// mythread.cc
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
#include "mythread.hpp"using std::cout;
using std::endl;void* start_handler(void* args)
{std::string name = static_cast<const char *>(args);while(true){cout << name << endl;sleep(1);}
}int main()
{Thread* thread1 = new Thread(start_handler, (void*)"thread one", 1);Thread* thread2 = new Thread(start_handler, (void*)"thread two", 2);Thread* thread3 = new Thread(start_handler, (void*)"thread three", 3);thread1->start();thread2->start();thread3->start();thread1->join();thread2->join();thread3->join();return 0;
}

在這里插入圖片描述



http://www.risenshineclean.com/news/5095.html

相關(guān)文章:

  • 許昌做網(wǎng)站公司專業(yè)做網(wǎng)站哪家好下載微信
  • 免費(fèi)做封面的網(wǎng)站新聞稿件
  • 建設(shè)校園網(wǎng)站必要性網(wǎng)站營銷網(wǎng)站營銷推廣
  • 左側(cè)導(dǎo)航網(wǎng)站站外推廣方式有哪些
  • 網(wǎng)站數(shù)據(jù)庫怎么做同步網(wǎng)站seo推廣營銷
  • 做磁力網(wǎng)站qq群怎么優(yōu)化排名靠前
  • 甘肅省城鄉(xiāng)住房建設(shè)廳網(wǎng)站首頁快速排名點(diǎn)擊工具
  • 做網(wǎng)站的策劃需要做什么seo免費(fèi)軟件
  • 可信網(wǎng)站辦理今天疫情最新消息
  • 護(hù)膚品網(wǎng)站建設(shè)前的行業(yè)分析營銷軟文怎么寫
  • 網(wǎng)易企業(yè)郵箱怎么發(fā)送文件seo網(wǎng)絡(luò)營銷技巧
  • 唐山企業(yè)建網(wǎng)站山東seo推廣公司
  • 旅游攻略網(wǎng)站開發(fā)龍崗網(wǎng)站設(shè)計
  • 鎮(zhèn)江門戶網(wǎng)站是哪個百度精準(zhǔn)引流推廣
  • 個人設(shè)計網(wǎng)站模板競價托管 微競價
  • 怎么做網(wǎng)站制作網(wǎng)絡(luò)營銷競價推廣
  • 修改網(wǎng)站需要什么凡科建站
  • 企業(yè)手機(jī)端網(wǎng)站設(shè)計模板有域名了怎么建立網(wǎng)站
  • 大豐網(wǎng)站建設(shè)全網(wǎng)推廣軟件
  • 寧波建設(shè)網(wǎng)站價格seo是怎么優(yōu)化推廣的
  • 網(wǎng)站建設(shè)的作用網(wǎng)站流量來源
  • 做游戲下載網(wǎng)站賺錢中央人民政府網(wǎng)
  • 什么網(wǎng)站做設(shè)計可以賺錢搜索引擎營銷是什么
  • 公司在網(wǎng)上做網(wǎng)站怎么做賬怎么注冊網(wǎng)站平臺
  • 無錫網(wǎng)站建設(shè)企業(yè)排名無錫營銷型網(wǎng)站制作
  • 制作網(wǎng)站的策劃方案網(wǎng)站自建
  • 17網(wǎng)站一起做網(wǎng)店不發(fā)貨網(wǎng)上售賣平臺有哪些
  • 做短視頻網(wǎng)站產(chǎn)品營銷方案案例范文
  • 網(wǎng)站怎么做谷歌權(quán)重色盲眼鏡
  • 武漢模板建站平臺哪家好鏈接提交工具