<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          多線程的使用

          共 7347字,需瀏覽 15分鐘

           ·

          2020-07-12 19:20

          0ef4985d65cb563651e7d967e2c30ca4.webp

          本章將分為兩大部分進(jìn)行講解,前半部分將引出線程的使用場景及基本概念,通過示例代碼來說明一個(gè)線程創(chuàng)建到退出到回收的基本流程。后半部分則會(huì)通過示例代碼來說明如何控制好線程,從臨界資源訪問與線程的執(zhí)行順序控制上引出互斥鎖、信號(hào)量的概念與使用方法。

          5.1 線程的使用

          5.1.1 為什么要使用多線程

          在編寫代碼時(shí),是否會(huì)遇到以下的場景會(huì)感覺到難以下手??

          場景一:寫程序在拷貝文件時(shí),需要一邊去拷貝文件,一邊去向用戶展示拷貝文件的進(jìn)度時(shí),傳統(tǒng)做法是通過每次拷貝完成結(jié)束后去更新變量,再將變量轉(zhuǎn)化為進(jìn)度顯示出來。其中經(jīng)歷了拷貝->計(jì)算->顯示->拷貝->計(jì)算->顯示...直至拷貝結(jié)束。

          這樣的程序架構(gòu)及其的低效,必須在單次拷貝結(jié)束后才可以刷新當(dāng)前拷貝進(jìn)度,若可以將進(jìn)程分支,一支單獨(dú)的解決拷貝問題,一支單獨(dú)的解決計(jì)算刷新問題,則程序效率會(huì)提升很多。

          場景二:用阻塞方式去讀取數(shù)據(jù),實(shí)時(shí)需要發(fā)送數(shù)據(jù)的時(shí)候。例如在進(jìn)行串口數(shù)據(jù)傳輸或者網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)臅r(shí)候,我們往往需要雙向通信,當(dāng)設(shè)置讀取數(shù)據(jù)為阻塞模式時(shí)候,傳統(tǒng)的單線程只能等到數(shù)據(jù)接收來臨后才能沖過阻塞,再根據(jù)邏輯進(jìn)行發(fā)送。

          當(dāng)我們要實(shí)現(xiàn)隨時(shí)發(fā)送、隨時(shí)接收時(shí),無法滿足我們的業(yè)務(wù)需求。若可以將進(jìn)程分支,一支單純的處理接收數(shù)據(jù)邏輯,一支單純的解決發(fā)送數(shù)據(jù)邏輯,就可以完美的實(shí)現(xiàn)功能。基于以上場景描述,多線程編程可以完美的解決上述問題。

          5.1.2 線程概念

          所謂線程,就是操作系統(tǒng)所能調(diào)度的最小單位。普通的進(jìn)程,只有一個(gè)線程在執(zhí)行對應(yīng)的邏輯。我們可以通過多線程編程,使一個(gè)進(jìn)程可以去執(zhí)行多個(gè)不同的任務(wù)。

          相比多進(jìn)程編程而言,線程享有共享資源,即在進(jìn)程中出現(xiàn)的全局變量,每個(gè)線程都可以去訪問它,與進(jìn)程共享“4G”內(nèi)存空間,使得系統(tǒng)資源消耗減少。本章節(jié)來討論Linux下POSIX線程。

          5.1.3 線程的標(biāo)識(shí)pthread_t

          對于進(jìn)程而言,每一個(gè)進(jìn)程都有一個(gè)唯一對應(yīng)的PID號(hào)來表示該進(jìn)程,而對于線程而言,也有一個(gè)“類似于進(jìn)程的PID號(hào)”,名為tid,其本質(zhì)是一個(gè)pthread_t類型的變量。線程號(hào)與進(jìn)程號(hào)是表示線程和進(jìn)程的唯一標(biāo)識(shí),但是對于線程號(hào)而言,其僅僅在其所屬的進(jìn)程上下文中才有意義。

          在程序中,可以通過函數(shù),pthread_self,來返回當(dāng)前線程的線程號(hào),例程1是打印線程tid號(hào)。

          獲取線程號(hào)
          #include?
          pthread_t?pthread_self(void);
          成功:返回線程號(hào)

          測試?yán)?:(Phtread_txex1.c)

          1?#include?
          2?#include?
          3
          4?int?main()
          5?
          {
          6??pthread_t?tid?=?pthread_self();//獲取主線程的tid號(hào)
          7??printf("tid?=?%lu\n",(unsigned?long)tid);
          8? return?0;
          9?}

          注意:

          因采用POSIX線程接口,故在要編譯的時(shí)候包含pthread庫,使用gcc編譯應(yīng)gcc xxx.c -lpthread 方可編譯多線程程序。

          編譯運(yùn)行結(jié)果:

          19c5b50d9aea26deff934b5bd634cd4e.webp

          5.1.4 線程的創(chuàng)建

          創(chuàng)建線程
          #include?
          int?pthread_create(pthread_t?*thread,?const?pthread_attr_t?*attr,void?*(*start_routine)?(void?*),?void?*arg);
          成功:返回0

          在傳統(tǒng)的程序中,一個(gè)進(jìn)程只有一個(gè)線程,可以通過函數(shù)pthread_create來創(chuàng)建線程。

          該函數(shù)第一個(gè)參數(shù)為pthread_t類型的線程號(hào)地址,當(dāng)函數(shù)執(zhí)行成功后會(huì)指向新建線程的線程號(hào);

          第二個(gè)參數(shù)表示了線程的屬性,一般傳入NULL表示默認(rèn)屬性;

          第三個(gè)參數(shù)代表返回值為void*,形參為void*的函數(shù)指針,當(dāng)線程創(chuàng)建成功后,會(huì)自動(dòng)的執(zhí)行該回調(diào)函數(shù);

          第四個(gè)參數(shù)則表示為向線程處理函數(shù)傳入的參數(shù),若不傳入,可用NULL填充,有關(guān)線程傳參后續(xù)小節(jié)會(huì)有詳細(xì)的說明,接下來通過一個(gè)簡單例程來使用該函數(shù)創(chuàng)建出一個(gè)線程。

          測試?yán)?:(Phtread_txex2.c)

          1??#include?
          2??#include?
          3??#include?
          4??#include?
          5?
          6??void?*fun(void?*arg)
          7??
          {
          8???printf("pthread_New?=?%lu\n",(unsigned?long)pthread_self());//打印線程的tid號(hào)
          9??}
          10
          11?int?main()
          12?
          {
          13
          14??pthread_t?tid1;
          15??int?ret?=?pthread_create(&tid1,NULL,fun,NULL);//創(chuàng)建線程
          16??if(ret?!=?0){
          17???perror("pthread_create");
          18???return?-1;
          19??}
          20
          21??/*tid_main?為通過pthread_self獲取的線程ID,tid_new通過執(zhí)行pthread_create成功后tid指向的空間*/
          22??printf("tid_main?=?%lu?tid_new?=?%lu?\n",(unsigned?long)pthread_self(),(unsigned?long)tid1);
          23??
          24??/*因線程執(zhí)行順序隨機(jī),不加sleep可能導(dǎo)致主線程先執(zhí)行,導(dǎo)致進(jìn)程結(jié)束,無法執(zhí)行到子線程*/
          25??sleep(1);
          26
          27??return?0;
          28?}
          29

          運(yùn)行結(jié)果:

          2e15b07bde9ffde19c39a0b7e4eb39ab.webp

          通過pthread_create確實(shí)可以創(chuàng)建出來線程,主線程中執(zhí)行pthread_create后的tid指向了線程號(hào)空間,與子線程通過函數(shù)pthread_self打印出來的線程號(hào)一致。

          特別說明的是,當(dāng)主線程伴隨進(jìn)程結(jié)束時(shí),所創(chuàng)建出來的線程也會(huì)立即結(jié)束,不會(huì)繼續(xù)執(zhí)行。并且創(chuàng)建出來的線程的執(zhí)行順序是隨機(jī)競爭的,并不能保證哪一個(gè)線程會(huì)先運(yùn)行。可以將上述代碼中sleep函數(shù)進(jìn)行注釋,觀察實(shí)驗(yàn)現(xiàn)象。

          去掉上述代碼25行后運(yùn)行結(jié)果:

          83c58249738ade21e688842168ab5b4d.webp

          上述運(yùn)行代碼3次,其中有2次被進(jìn)程結(jié)束,無法執(zhí)行到子線程的邏輯,最后一次則執(zhí)行到了子線程邏輯后結(jié)束的進(jìn)程。

          因此可以說明,線程的執(zhí)行順序不受控制,且整個(gè)進(jìn)程結(jié)束后所產(chǎn)生的線程也隨之被釋放,在后續(xù)內(nèi)容中將會(huì)描述如何控制線程執(zhí)行。

          5.1.5 向線程傳入?yún)?shù)

          pthread_create()的最后一個(gè)參數(shù)的為void類型的數(shù)據(jù),表示可以向線程傳遞一個(gè)void數(shù)據(jù)類型的參數(shù),線程的回調(diào)函數(shù)中可以獲取該參數(shù),例程3舉例了如何向線程傳入變量地址與變量值。

          測試?yán)?:(Phtread_txex3.c)

          1??#include?
          2??#include?
          3??#include?
          4??#include?
          5?
          6??void?*fun1(void?*arg)
          7??
          {
          8???printf("%s:arg?=?%d?Addr?=?%p\n",__FUNCTION__,*(int?*)arg,arg);
          9??}
          10
          11?void?*fun2(void?*arg)
          12?
          {
          13??printf("%s:arg?=?%d?Addr?=?%p\n",__FUNCTION__,(int)(long)arg,arg);
          14?}
          15
          16?int?main()
          17?
          {
          18
          19??pthread_t?tid1,tid2;
          20??int?a?=?50;
          21??int?ret?=?pthread_create(&tid1,NULL,fun1,(void?*)&a);//創(chuàng)建線程傳入變量a的地址
          22??if(ret?!=?0){
          23???perror("pthread_create");
          24???return?-1;
          25??}
          27??ret?=?pthread_create(&tid2,NULL,fun2,(void?*)(long)a);//創(chuàng)建線程傳入變量a的值
          28??if(ret?!=?0){
          29???perror("pthread_create");
          30???return?-1;
          31??}
          32??sleep(1);
          33??printf("%s:a?=?%d?Add?=?%p?\n",__FUNCTION__,a,&a);
          34??return?0;
          35?}
          36

          運(yùn)行結(jié)果:

          84b7777e38623757114a13a5ff96b09f.webp

          本例程展示了如何利用線程創(chuàng)建函數(shù)的第四個(gè)參數(shù)向線程傳入數(shù)據(jù),舉例了如何以地址的方式傳入值、以變量的方式傳入值,例程代碼的21行,是將變量a先行取地址后,再次強(qiáng)制類型轉(zhuǎn)化為void后傳入線程,線程處理的回調(diào)函數(shù)中,先將萬能指針void轉(zhuǎn)化為int*,再次取地址就可以獲得該地址變量的值,其本質(zhì)在于地址的傳遞。例程代碼的27行,直接將int類型的變量強(qiáng)制轉(zhuǎn)化為void進(jìn)行傳遞(針對不同位數(shù)機(jī)器,指針對其字?jǐn)?shù)不同,需要int轉(zhuǎn)化為long在轉(zhuǎn)指針,否則可能會(huì)發(fā)生警告),在線程處理回調(diào)函數(shù)中,直接將void數(shù)據(jù)轉(zhuǎn)化為int類型即可,本質(zhì)上是在傳遞變量a的值。

          上述兩種方法均可得到所要的值,但是要注意其本質(zhì),一個(gè)為地址傳遞,一個(gè)為值的傳遞。當(dāng)變量發(fā)生改變時(shí)候,傳遞地址后,該地址所對應(yīng)的變量也會(huì)發(fā)生改變,但傳入變量值的時(shí)候,即使地址指針?biāo)傅淖兞堪l(fā)生變化,但傳入的為變量值,不會(huì)受到指針的指向的影響,實(shí)際項(xiàng)目中切記兩者之間的區(qū)別。具體說明見例程4.

          測試?yán)?:(Phtread_txex4.c)

          1??#include?
          2??#include?
          3??#include?
          4??#include?
          5?
          6??void?*fun1(void?*arg)
          7??
          {
          8???while(1){
          9???
          10???printf("%s:arg?=?%d?Addr?=?%p\n",__FUNCTION__,*(int?*)arg,arg);
          11???sleep(1);
          12??}
          13?}
          14
          15?void?*fun2(void?*arg)
          16?
          {
          17??while(1){
          18??
          19???printf("%s:arg?=?%d?Addr?=?%p\n",__FUNCTION__,(int)(long)arg,arg);
          20???sleep(1);
          21??}
          22?}
          23
          24?int?main()
          25?
          {
          26
          27??pthread_t?tid1,tid2;
          28??int?a?=?50;
          29??int?ret?=?pthread_create(&tid1,NULL,fun1,(void?*)&a);
          30??if(ret?!=?0){
          31???perror("pthread_create");
          32???return?-1;
          33??}
          34??sleep(1);
          35??ret?=?pthread_create(&tid2,NULL,fun2,(void?*)(long)a);
          36??if(ret?!=?0){
          37???perror("pthread_create");
          38???return?-1;
          39??}
          40??while(1){
          41???a++;
          42???sleep(1);
          43???printf("%s:a?=?%d?Add?=?%p?\n",__FUNCTION__,a,&a);
          44??}
          45??return?0;
          46?}
          47

          運(yùn)行結(jié)果:

          b0ef2b1f81dc03429f28910d54ada873.webp

          上述例程講述了如何向線程傳遞一個(gè)參數(shù),在處理實(shí)際項(xiàng)目中,往往會(huì)遇到傳遞多個(gè)參數(shù)的問題,我們可以通過結(jié)構(gòu)體來進(jìn)行傳遞,解決此問題。

          測試?yán)?:(Phtread_txex5.c)

          1??#include?
          2??#include?
          3??#include?
          4??#include?
          5??#include?
          6?
          7??struct?Stu{
          8???int?Id;
          9???char?Name[32];
          10??float?Mark;
          11?};
          12
          13?void?*fun1(void?*arg)
          14?
          {
          15??struct?Stu?*tmp?=?(struct?Stu?*)arg;
          16??printf("%s:Id?=?%d?Name?=?%s?Mark?=?%.2f\n",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);
          17??
          18?}
          19
          20?int?main()
          21?
          {
          22
          23??pthread_t?tid1,tid2;
          24??struct?Stu?stu;
          25??stu.Id?=?10000;
          26??strcpy(stu.Name,"ZhangSan");
          27??stu.Mark?=?94.6;
          28
          29??int?ret?=?pthread_create(&tid1,NULL,fun1,(void?*)&stu);
          30??if(ret?!=?0){
          31???perror("pthread_create");
          32???return?-1;
          33??}
          34??printf("%s:Id?=?%d?Name?=?%s?Mark?=?%.2f\n",__FUNCTION__,stu.Id,stu.Name,stu.Mark);
          35??sleep(1);
          36??return?0;
          37?}
          38

          運(yùn)行結(jié)果:

          55a705ca7a3b87ddac709e6d384bca19.webp

          5.1.6 線程的退出與回收

          線程的退出情況有三種:第一種是進(jìn)程結(jié)束,進(jìn)程中所有的線程也會(huì)隨之結(jié)束。第二種是通過函數(shù)pthread_exit來主動(dòng)的退出線程。第三種通過函數(shù)pthread_cancel被其他線程被動(dòng)結(jié)束。

          當(dāng)線程結(jié)束后,主線程可以通過函數(shù)pthread_join/pthread_tryjoin_np來回收線程的資源,并且獲得線程結(jié)束后需要返回的數(shù)據(jù)。

          線程退出
          #include?
          void?pthread_exit(void?*retval);

          該函數(shù)為線程退出函數(shù),在退出時(shí)候可以傳遞一個(gè)void*類型的數(shù)據(jù)帶給主線程,若選擇不傳出數(shù)據(jù),可將參數(shù)填充為NULL。

          線程資源回收(阻塞)
          #include?
          int?pthread_join(pthread_t?thread,?void?**retval);
          成功:返回0

          該函數(shù)為線程回收函數(shù),默認(rèn)狀態(tài)為阻塞狀態(tài),直到成功回收線程后被沖開阻塞。第一個(gè)參數(shù)為要回收線程的tid號(hào),第二個(gè)參數(shù)為線程回收后接受線程傳出的數(shù)據(jù)。

          線程資源回收(非阻塞)
          #define?_GNU_SOURCE????????????
          #include?
          ?int?pthread_tryjoin_np(pthread_t?thread,?void?**retval);
          成功:返回0

          該函數(shù)為非阻塞模式回收函數(shù),通過返回值判斷是否回收掉線程,成功回收則返回0,其余參數(shù)與pthread_join一致。

          該函數(shù)傳入一個(gè)tid號(hào),會(huì)強(qiáng)制退出該tid所指向的線程,若成功執(zhí)行會(huì)返回0:

          線程退出(指定線程號(hào))
          #include?
          int?pthread_cancel(pthread_t?thread);
          成功:返回0

          上述描述簡單的介紹了有關(guān)線程回收的API,下面通過例程來說明上述API。

          測試?yán)?:(Phtread_txex6.c)


          1??#include?
          2??#include?
          3??#include?
          4??#include?
          5?
          6??void?*fun1(void?*arg)
          7??
          {
          8???static?int?tmp?=?0;//必須要static修飾,否則pthread_join無法獲取到正確值
          9???//int?tmp?=?0;
          10??tmp?=?*(int?*)arg;
          11??tmp+=100;
          12??printf("%s:Addr?=?%p?tmp?=?%d\n",__FUNCTION__,&tmp,tmp);
          13??pthread_exit((void?*)&tmp);//將變量tmp取地址轉(zhuǎn)化為void*類型傳出
          14?}
          15
          16
          17?int?main()
          18?
          {
          19
          20??pthread_t?tid1;
          21??int?a?=?50;
          22??void?*Tmp?=?NULL;//因pthread_join第二個(gè)參數(shù)為void**類型
          23??int?ret?=?pthread_create(&tid1,NULL,fun1,(void?*)&a);
          24??if(ret?!=?0){
          25???perror("pthread_create");
          26???return?-1;
          27??}
          28??pthread_join(tid1,&Tmp);
          29??printf("%s:Addr?=?%p?Val?=?%d\n",__FUNCTION__,Tmp,*(int?*)Tmp);
          30??return?0;
          31?}
          32

          運(yùn)行結(jié)果:

          a729a8369ed796ac6b67e6e6ebcfe115.webp

          上述例程先通過23行將變量以地址的形式傳入線程,在線程中做出了自加100的操作,當(dāng)線程退出的時(shí)候通過線程傳參,用void*類型的數(shù)據(jù)通過pthread_join接受。

          此例程去掉了之前加入的sleep函數(shù),原因是pthread_join函數(shù)具備阻塞的特性,直至成功收回掉線程后才會(huì)沖破阻塞,因此不需要靠考慮主線程會(huì)執(zhí)行到30行結(jié)束進(jìn)程的情況。

          特別要說明的是例程第8行,當(dāng)變量從線程傳出的時(shí)候,需要加static修飾,對生命周期做出延續(xù),否則無法傳出正確的變量值。

          測試?yán)?:(Phtread_txex7.c)

          1??#define?_GNU_SOURCE?
          2??#include?
          3??#include?
          4??#include?
          5??#include?
          6?
          7??void?*fun(void?*arg)
          8??
          {
          9???printf("Pthread:%d?Come?!\n",(int?)(long)arg+1);
          10??pthread_exit(arg);
          11?}
          12
          13
          14?int?main()
          15?
          {
          16??int?ret,i,flag?=?0;
          17??void?*Tmp?=?NULL;
          18??pthread_t?tid[3];
          19??for(i?=?0;i?3;i++){
          20???ret?=?pthread_create(&tid[i],NULL,fun,(void?*)(long)i);
          21???if(ret?!=?0){
          22????perror("pthread_create");
          23????return?-1;
          24???}
          25??}
          26??while(1){//通過非阻塞方式收回線程,每次成功回收一個(gè)線程變量自增,直至3個(gè)線程全數(shù)回收
          27???for(i?=?0;i?<3;i++){
          28????if(pthread_tryjoin_np(tid[i],&Tmp)?==?0){
          29?????printf("Pthread?:?%d?exit?!\n",(int?)(long?)Tmp+1);
          30?????flag++;?
          31????}
          32???}
          33???if(flag?>=?3)?break;
          34??}
          35??return?0;
          36?}
          37

          運(yùn)行結(jié)果:

          86bee9f4c606a6f0a0f29d0c24615e48.webp

          例程7展示了如何使用非阻塞方式來回收線程,此外也展示了多個(gè)線程可以指向同一個(gè)回調(diào)函數(shù)的情況。例程6通過阻塞方式回收線程幾乎規(guī)定了線程回收的順序,若最先回收的線程未退出,則一直會(huì)被阻塞,導(dǎo)致后續(xù)先退出的線程無法及時(shí)的回收。

          通過函數(shù)pthread_tryjoin_np,使用非阻塞回收,線程可以根據(jù)退出先后順序自由的進(jìn)行資源的回收。

          測試?yán)?:(Phtread_txex8.c)

          1??#define?_GNU_SOURCE?
          2??#include?
          3??#include?
          4??#include?
          5??#include?
          6?
          7??void?*fun1(void?*arg)
          8??
          {
          9???printf("Pthread:1?come!\n");
          10??while(1){
          11???sleep(1);
          12??}
          13?}
          14
          15?void?*fun2(void?*arg)
          16?
          {
          17??printf("Pthread:2?come!\n");
          18??pthread_cancel((pthread_t?)(long)arg);//殺死線程1,使之強(qiáng)制退出
          19??pthread_exit(NULL);
          20?}
          21
          22?int?main()
          23?
          {
          24??int?ret,i,flag?=?0;
          25??void?*Tmp?=?NULL;
          26??pthread_t?tid[2];
          27??ret?=?pthread_create(&tid[0],NULL,fun1,NULL);
          28??if(ret?!=?0){
          29???perror("pthread_create");
          30???return?-1;
          31??}
          32??sleep(1);
          33??ret?=?pthread_create(&tid[1],NULL,fun2,(void?*)tid[0]);//傳輸線程1的線程號(hào)
          34??if(ret?!=?0){
          35???perror("pthread_create");
          36???return?-1;
          37??}
          38??while(1){//通過非阻塞方式收回線程,每次成功回收一個(gè)線程變量自增,直至2個(gè)線程全數(shù)回收

          39???for(i?=?0;i?<2;i++){
          40????if(pthread_tryjoin_np(tid[i],NULL)?==?0){
          41?????printf("Pthread?:?%d?exit?!\n",i+1);
          42?????flag++;?
          43????}
          44???}
          45???if(flag?>=?2)?break;
          46??}
          47??return?0;
          48?}
          49

          運(yùn)行結(jié)果:

          20c8386e8550f9325ffe4c5fd0e6bc73.webp

          例程8展示了如何利用pthread_cancel函數(shù)主動(dòng)的將某個(gè)線程結(jié)束。27行與33行創(chuàng)建了線程,將第一個(gè)線程的線程號(hào)傳參形式傳入了第二個(gè)線程。

          第一個(gè)的線程執(zhí)行死循環(huán)睡眠邏輯,理論上除非進(jìn)程結(jié)束,其永遠(yuǎn)不會(huì)結(jié)束,但在第二個(gè)線程中調(diào)用了pthread_cancel函數(shù),相當(dāng)于向該線程發(fā)送一個(gè)退出的指令,導(dǎo)致線程被退出,最終資源被非阻塞回收掉。

          此例程要注意第32行的sleep函數(shù),一定要確保線程1先執(zhí)行,因線程是無序執(zhí)行,故加入該睡眠函數(shù)控制順序,在本章后續(xù),會(huì)講解通過加鎖、信號(hào)量等手段來合理的控制線程的臨界資源訪問與線程執(zhí)行順序控制。

          推薦閱讀:專輯|Linux文章匯總專輯|程序人生專輯|C語言



          0f5eb6ed6e9666497aa1416dbb8b4ae8.webp

          嵌入式Linux
          微信掃描二維碼,關(guān)注我的公眾號(hào)
          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美性A V | 亚洲88 | 毛A黄片一级 | 国产 日韩 欧美视频在线 | 97人妻人人揉人人躁人人 |