go語言的讀寫鎖和互懟鎖 go互斥鎖和讀寫鎖區(qū)別

Go語言設(shè)計與實現(xiàn)(上)

基本設(shè)計思路:

成都創(chuàng)新互聯(lián)公司服務(wù)項目包括舟山網(wǎng)站建設(shè)、舟山網(wǎng)站制作、舟山網(wǎng)頁制作以及舟山網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,舟山網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到舟山省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!

類型轉(zhuǎn)換、類型斷言、動態(tài)派發(fā)。iface,eface。

反射對象具有的方法:

編譯優(yōu)化:

內(nèi)部實現(xiàn):

實現(xiàn) Context 接口有以下幾個類型(空實現(xiàn)就忽略了):

互斥鎖的控制邏輯:

設(shè)計思路:

(以上為寫被讀阻塞,下面是讀被寫阻塞)

總結(jié),讀寫鎖的設(shè)計還是非常巧妙的:

設(shè)計思路:

WaitGroup 有三個暴露的函數(shù):

部件:

設(shè)計思路:

結(jié)構(gòu):

Once 只暴露了一個方法:

實現(xiàn):

三個關(guān)鍵點:

細節(jié):

讓多協(xié)程任務(wù)的開始執(zhí)行時間可控(按順序或歸一)。(Context 是控制結(jié)束時間)

設(shè)計思路: 通過一個鎖和內(nèi)置的 notifyList 隊列實現(xiàn),Wait() 會生成票據(jù),并將等待協(xié)程信息加入鏈表中,等待控制協(xié)程中發(fā)送信號通知一個(Signal())或所有(Boardcast())等待者(內(nèi)部實現(xiàn)是通過票據(jù)通知的)來控制協(xié)程解除阻塞。

暴露四個函數(shù):

實現(xiàn)細節(jié):

部件:

包: golang.org/x/sync/errgroup

作用:開啟 func() error 函數(shù)簽名的協(xié)程,在同 Group 下協(xié)程并發(fā)執(zhí)行過程并收集首次 err 錯誤。通過 Context 的傳入,還可以控制在首次 err 出現(xiàn)時就終止組內(nèi)各協(xié)程。

設(shè)計思路:

結(jié)構(gòu):

暴露的方法:

實現(xiàn)細節(jié):

注意問題:

包: "golang.org/x/sync/semaphore"

作用:排隊借資源(如錢,有借有還)的一種場景。此包相當于對底層信號量的一種暴露。

設(shè)計思路:有一定數(shù)量的資源 Weight,每一個 waiter 攜帶一個 channel 和要借的數(shù)量 n。通過隊列排隊執(zhí)行借貸。

結(jié)構(gòu):

暴露方法:

細節(jié):

部件:

細節(jié):

包: "golang.org/x/sync/singleflight"

作用:防擊穿。瞬時的相同請求只調(diào)用一次,response 被所有相同請求共享。

設(shè)計思路:按請求的 key 分組(一個 *call 是一個組,用 map 映射存儲組),每個組只進行一次訪問,組內(nèi)每個協(xié)程會獲得對應結(jié)果的一個拷貝。

結(jié)構(gòu):

邏輯:

細節(jié):

部件:

如有錯誤,請批評指正。

線程同步互斥鎖和讀寫鎖的區(qū)別和各自適用場景

線程同步的方式包括:互斥鎖、讀寫鎖、條件變量、信號量和令牌。互斥鎖和讀寫鎖:提供對臨界資源的保護,當多線程試圖訪問臨界資源時,都必須通過獲取鎖的方式來訪問臨界資源。(臨界資源:是被多線程共享的資源)當讀寫線程獲取鎖的頻率差別不大時,一般采用互斥鎖,如果讀線程訪問臨界資源的頻率大于寫線程,這個時候采用讀寫鎖較為合適,讀寫鎖允許多個讀線程同時訪問臨界資源,讀寫線程必須互斥訪問臨界資源。讀寫鎖的實現(xiàn)采用了互斥鎖,所以在讀寫次數(shù)差不多的情況下采用讀寫鎖性能沒有直接采用互斥鎖來的高。

信號量,互斥鎖,讀寫鎖和條件變量的區(qū)別

信號量強調(diào)的是線程(或進程)間的同步:“信號量用在多線程多任務(wù)同步的,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作(大家都 在sem_wait的時候,就阻塞在那里)。當信號量為單值信號量是,也可以完成一個資源的互斥訪問。

有名信號量:可以用于不同進程間或多線程間的互斥與同步

創(chuàng)建打開有名信號量

sem_t *sem_open(const char *name, int oflag);

sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

成功返回信號量指針;失敗返回SEM_FAILED,設(shè)置errnoname是文件路徑名,但不能寫成/tmp/a.sem這樣的形式,因為在linux下,sem都是在/dev/shm目錄下,可寫成"/mysem"或"mysem",創(chuàng)建出來的文件都 是"/dev/shm/sem.mysem",mode設(shè)置為0666,value設(shè)置為信號量的初始值.所需信號燈等已存在條件下指定O_CREAT|O_EXCL卻是個錯誤。

關(guān)閉信號量,進程終止時,會自動調(diào)用它

int sem_close(sem_t *sem);

成功返回0;失敗返回-1,設(shè)置errno

刪除信號量,立即刪除信號量名字,當其他進程都關(guān)閉它時,銷毀它

int sem_unlink(const char *name);

等待信號量,測試信號量的值,如果其值小于或等于0,那么就等待(阻塞);一旦其值變?yōu)榇笥?就將它減1,并返回

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

成功返回0;失敗返回-1,設(shè)置errno

當信號量的值為0時,sem_trywait立即返回,設(shè)置errno為EAGAIN。如果被某個信號中斷,sem_wait會過早地返回,設(shè)置errno為EINTR

發(fā)出信號量,給它的值加1,然后喚醒正在等待該信號量的進程或線程

int sem_post(sem_t *sem);

成功返回0;失敗返回-1,不會改變它的值,設(shè)置errno,該函數(shù)是異步信號安全的,可以在信號處理程序里調(diào)用它無名信號量,用于進程體內(nèi)各線程間的互斥和同步,使用如下API(無名信號量,基于內(nèi)存的信號量)

(1)、sem_init

功能:用于創(chuàng)建一個信號量,并初始化信號量的值。

頭文件:

函數(shù)原型: int sem_init (sem_t* sem, int pshared, unsigned int value);

函數(shù)傳入值: sem:信號量。pshared:決定信號量能否在幾個進程間共享。由于目前LINUX還沒有實現(xiàn)進程間共享信息量,所以這個值只能取0。

(2)其他函數(shù)。

int sem_wait (sem_t* sem);

int sem_trywait (sem_t* sem);

int sem_post (sem_t* sem);

int sem_getvalue (sem_t* sem);

int sem_destroy (sem_t* sem);

功能:sem_wait和sem_trywait相當于P操作,它們都能將信號量的值減一,兩者的區(qū)別在于若信號量的值小于零時,sem_wait將會阻塞進程,而sem_trywait則會立即返回。sem_post相當于V操作,它將信號量的值加一,同時發(fā)出喚醒的信號給等待的進程(或線程)。

sem_getvalue 得到信號量的值。

sem_destroy 摧毀信號量。

如果某個基于內(nèi)存的信號燈是在不同進程間同步的,該信號燈必須存放在共享內(nèi)存區(qū)中,這要只要該共享內(nèi)存區(qū)存在,該信號燈就存在。

互斥鎖(又名互斥量)強調(diào)的是資源的訪問互斥:互斥鎖是用在多線程多任務(wù)互斥的,一個線程占用了某一個資源,那么別的線程就無法訪問,直到這個線程unlock,其他的線程才開始可以利用這個資源。比如對全局變量的訪問,有時要加鎖,操作完了,在解鎖。有的時候鎖和信號量會同時使用的”

也就是說,信號量不一定是鎖定某一個資源,而是流程上的概念,比如:有A,B兩個線程,B線程要等A線程完成某一任務(wù)以后再進行自己下面的步驟,這個任務(wù)并不一定是鎖定某一資源,還可以是進行一些計算或者數(shù)據(jù)處理之類。而線程互斥量則是“鎖住某一資源”的概念,在鎖定期間內(nèi),其他線程無法對被保護的數(shù)據(jù)進行操作。在有些情況下兩者可以互換。

在linux下, 線程的互斥量數(shù)據(jù)類型是pthread_mutex_t. 在使用前, 要對它進行初始化:

對于靜態(tài)分配的互斥量, 可以把它設(shè)置為PTHREAD_MUTEX_INITIALIZER, 或者調(diào)用pthread_mutex_init.

對于動態(tài)分配的互斥量, 在申請內(nèi)存(malloc)之后, 通過pthread_mutex_init進行初始化, 并且在釋放內(nèi)存(free)前需要調(diào)用pthread_mutex_destroy.

原型:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

頭文件:

返回值: 成功則返回0, 出錯則返回錯誤編號.

說明: 如果使用默認的屬性初始化互斥量, 只需把attr設(shè)為NULL. 其他值在以后講解.

首先說一下加鎖函數(shù):

頭文件:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

返回值: 成功則返回0, 出錯則返回錯誤編號.

說 明: 具體說一下trylock函數(shù), 這個函數(shù)是非阻塞調(diào)用模式, 也就是說, 如果互斥量沒被鎖住, trylock函數(shù)將把互斥量加鎖, 并獲得對共享資源的訪問權(quán)限; 如果互斥量 被鎖住了, trylock函數(shù)將不會阻塞等待而直接返回EBUSY, 表示共享資源處于忙狀態(tài).

再說一下解所函數(shù):

頭文件:

原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值: 成功則返回0, 出錯則返回錯誤編號.

條件變量常與互斥鎖同時使用,達到線程同步的目的:條件變量通過允許線程阻塞和等待另一個線程發(fā)送信號的方法彌補了互斥鎖的不足。在發(fā) 送信號時,如果沒有線程 等待在該條件變量上,那么信號將丟失;而信號量有計數(shù)值,每次信號量post操作都會被記錄

1. 互斥鎖必須是誰上鎖就由誰來解鎖,而信號量的wait和post操作不必由同一個線程執(zhí)行。

2. 互斥鎖要么被鎖住,要么被解開,和二值信號量類似

3. sem_post是各種同步技巧中,唯一一個能在信號處理程序中安全調(diào)用的函數(shù)

4. 互斥鎖是為上鎖而優(yōu)化的;條件變量是為等待而優(yōu)化的; 信號量既可用于上鎖,也可用于等待,因此會有更多的開銷和更高的復雜性

5. 互斥鎖,條件變量都只用于同一個進程的各線程間,而信號量(有名信號量)可用于不同進程間的同步。當信號量用于進程間同步時,要求信號量建立在共享內(nèi)存區(qū)。

6. 信號量有計數(shù)值,每次信號量post操作都會被記錄,而條件變量在發(fā)送信號時,如果沒有線程在等待該條件變量,那么信號將丟失。

讀寫鎖

讀寫鎖與互斥量類似,不過讀寫鎖允許更高的并行性?;コ饬恳词擎i住狀態(tài)要么是不加鎖狀態(tài),而且一次只有一個線程可以對其加鎖。

讀寫鎖可以由三種狀態(tài):讀模式下加鎖狀態(tài)、寫模式下加鎖狀態(tài)、不加鎖狀態(tài)。一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫

鎖。

在讀寫鎖是寫加鎖狀態(tài)時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖在讀加鎖狀態(tài)時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權(quán),但是如果線程希望以寫模式對此鎖進行加鎖,它必須阻塞直到所有的線程釋放讀鎖。雖然讀寫鎖的實現(xiàn)各不相同,但當讀寫鎖處于讀模式鎖住狀態(tài)時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨后的讀模式鎖請求。這樣可以避免讀模式鎖長期占用,而等待的寫模式鎖請求一直得不到滿足。

讀寫鎖非常適合于對數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠大于寫的情況。當讀寫鎖在寫模式下時,它所保護的數(shù)據(jù)結(jié)構(gòu)就可以被安全地修改,因為當前只有一個線程可以在寫模式下?lián)?有這個鎖。當讀寫鎖在讀狀態(tài)下時,只要線程獲取了讀模式下的讀寫鎖,該鎖所保護的數(shù)據(jù)結(jié)構(gòu)可以被多個獲得讀模式鎖的線程讀取。

讀寫鎖也叫做共享-獨占鎖,當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當他以寫模式鎖住時,它是以獨占模式鎖住的。

初始化和銷毀:

#include

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

成功則返回0, 出錯則返回錯誤編號.

同互斥量以上, 在釋放讀寫鎖占用的內(nèi)存之前, 需要先通過thread_rwlock_destroy對讀寫鎖進行清理工作, 釋放由init分配的資源.

讀和寫:

#include

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

成功則返回0, 出錯則返回錯誤編號.

這3個函數(shù)分別實現(xiàn)獲取讀鎖, 獲取寫鎖和釋放鎖的操作. 獲取鎖的兩個函數(shù)是阻塞操作, 同樣, 非阻塞的函數(shù)為:

#include

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

成功則返回0, 出錯則返回錯誤編號.

非阻塞的獲取鎖操作, 如果可以獲取則返回0, 否則返回錯誤的EBUSY.

雖然讀寫鎖提高了并行性,但是就速度而言并不比互斥量快.

可能這也是即使有讀寫鎖存在還會使用互斥量的原因,因為他在速度方面略勝一籌。這就需要我們在寫程序的時候綜合考慮速度和并行性并找到一個折中。

比如: 假設(shè)使用互斥量需要0.5秒,使用讀寫鎖需要0.8秒。在類似學生管理系統(tǒng)這類軟件中,可能百分之九十的時間都是查詢操作,那么假如現(xiàn)在突然來個個20個請求,如果使用的是互斥量,那么最后的那個查詢請求被滿足需要10后。這樣,估計沒人能受得了。而使用讀寫鎖,應為 讀鎖能夠多次獲得。所以所有的20個請求,每個請求都能在1秒左右得到滿足。

也就是說,在一些寫操作比較多或是本身需要同步的地方并不多的程序中我們應該使用互斥量,而在讀操作遠大于寫操作的一些程序中我們應該使用讀寫鎖來進行同步

條件變量(condition)

條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發(fā)生。

條件本身是由互斥量保護的。線程在改變條件狀態(tài)前必須首先鎖住互斥量,其它線程在獲得互斥量之前不會察覺到這種改變,因此必須鎖定互斥量以后才能計算條件。

條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態(tài)改變的互斥鎖。如果另一個線程改變了條件,它發(fā)信號給關(guān)聯(lián)的條件

變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內(nèi)存,條件變量可以被用來實現(xiàn)這兩進程間的線程同步。

1. 初始化:

條件變量采用的數(shù)據(jù)類型是pthread_cond_t, 在使用之前必須要進行初始化, 這包括兩種方式:

靜態(tài): 可以把常量PTHREAD_COND_INITIALIZER給靜態(tài)分配的條件變量.

動態(tài): pthread_cond_init函數(shù), 是釋放動態(tài)條件變量的內(nèi)存空間之前, 要用pthread_cond_destroy對其進行清理.

#include

int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

成功則返回0, 出錯則返回錯誤編號.

注意:條件變量占用的空間并未被釋放。

當pthread_cond_init的attr參數(shù)為NULL時, 會創(chuàng)建一個默認屬性的條件變量; 非默認情況以后討論.

2. 等待條件:

#include

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

成功則返回0, 出錯則返回錯誤編號.

這兩個函數(shù)分別是阻塞等待和超時等待.

等待條件函數(shù)等待條件變?yōu)檎? 傳遞給pthread_cond_wait的互斥量對條件進行保護, 調(diào)用者把鎖住的互斥量傳遞給函數(shù). 函數(shù)把調(diào)用線程放到等待條件的線程列表上, 然后對互斥量解鎖, 這兩個操作是原子的. 這樣 便關(guān)閉了條件檢查和線程進入休眠狀態(tài)等待條件改變這兩個操作之間的時間通道, 這樣線程就不會錯過條件的任何變化.

當pthread_cond_wait返回時, 互斥量再次被鎖住.

pthread_cond_wait函數(shù)的返回并不意味著條件的值一定發(fā)生了變化,必須重新檢查條件的值。

pthread_cond_wait函數(shù)返回時,相應的互斥鎖將被當前線程鎖定,即使是函數(shù)出錯返回。

阻塞在條件變量上的線程被喚醒以后,直到pthread_cond_wait()函數(shù)返回之前條件的值都有可能發(fā)生變化。所以函數(shù)返回以后,在鎖定相應的互斥鎖之前,必須重新測試條 件值。最好的測試方法是循環(huán)調(diào)用pthread_cond_wait函數(shù),并把滿足條件的表達式置為循環(huán)的終止條件。如:

pthread_mutex_lock();

while (condition_is_false)

pthread_cond_wait();

pthread_mutex_unlock();

阻塞在同一個條件變量上的不同線程被釋放的次序是不一定的。

注意:pthread_cond_wait()函數(shù)是退出點,如果在調(diào)用這個函數(shù)時,已有一個掛起的退出請求,且線程允許退出,這個線程將被終止并開始執(zhí)行善后處理函數(shù),而這時和條 件變量相關(guān)的互斥鎖仍將處在鎖定狀態(tài)。

pthread_cond_timedwait函數(shù)到了一定的時間,即使條件未發(fā)生也會解除阻塞。這個時間由參數(shù)abstime指定。函數(shù)返回時,相應的互斥鎖往往是鎖定的,即使是函數(shù)出錯返回。

注意:pthread_cond_timedwait函數(shù)也是退出點。

超時時間參數(shù)是指一天中的某個時刻。使用舉例:

pthread_timestruc_t to;

to.tv_sec = time(NULL) + TIMEOUT;

to.tv_nsec = 0;

超時返回的錯誤碼是ETIMEDOUT。

3. 通知條件:

#include

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

成功則返回0, 出錯則返回錯誤編號.

這兩個函數(shù)用于通知線程條件已經(jīng)滿足. 調(diào)用這兩個函數(shù), 也稱向線程或條件發(fā)送信號. 必須注意, 一定要在改變條件狀態(tài)以后再給線程發(fā)送信號.

POS線程同步互斥鎖和讀寫鎖的區(qū)別和各自適用場景

線程同步的方式包括:互斥鎖、讀寫鎖、條件變量、信號量和令牌。

以Java語言為例:

用synchronized關(guān)鍵字修飾同步方法。

同步有幾種實現(xiàn)方法分別是synchronized,wait與notify

wait():使一個線程處于等待狀態(tài),并且釋放所持有的對象的lock。

sleep():使一個正在運行的線程處于睡眠狀態(tài),是一個靜態(tài)方法,調(diào)用此方法要捕捉InterruptedException異常。

notify():喚醒一個處于等待狀態(tài)的線程,注意的是在調(diào)用此方法的時候,并不能確切的喚醒某一個等待狀態(tài)的線程,而是由JVM確定喚醒哪個線程,而且不是按優(yōu)先級。

Allnotity():喚醒所有處入等待狀態(tài)的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。

同步是多線程中的重要概念。同步的使用可以保證在多線程運行的環(huán)境中,程序不會產(chǎn)生設(shè)計之外的錯誤結(jié)果。同步的實現(xiàn)方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關(guān)鍵字。

給一個方法增加synchronized修飾符之后就可以使它成為同步方法,這個方法可以是靜態(tài)方法和非靜態(tài)方法,但是不能是抽象類的抽象方法,也不能是接口中的接口方法。下面代碼是一個同步方法的示例:

public synchronized void aMethod() {

// do something

}

public static synchronized void anotherMethod() {

// do something

}

線程在執(zhí)行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執(zhí)行完它所調(diào)用的同步方法并從中退出,從而導致它釋放了該對象的同步鎖之后。在一個對象被某個線程鎖定之后,其他線程是可以訪問這個對象的所有非同步方法的。

同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊里的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。如果這個方法是靜態(tài)同步方法呢?那么線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限于同一個對象之間,所以靜態(tài)同步方法只受它所屬類的其它靜態(tài)同步方法的制約,而跟這個類的實例(對象)沒有關(guān)系。

網(wǎng)頁名稱:go語言的讀寫鎖和互懟鎖 go互斥鎖和讀寫鎖區(qū)別
文章URL:http://www.muchs.cn/article40/hhideo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈、服務(wù)器托管網(wǎng)站排名、虛擬主機、全網(wǎng)營銷推廣、網(wǎng)站收錄

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

營銷型網(wǎng)站建設(shè)