a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > C# > 正文

c#多線程編程基礎(chǔ)

2020-01-24 02:48:46
字體:
供稿:網(wǎng)友

無論您是為具有單個(gè)處理器的計(jì)算機(jī)還是為具有多個(gè)處理器的計(jì)算機(jī)進(jìn)行開發(fā),您都希望應(yīng)用程序?yàn)橛脩籼峁┳詈玫捻憫?yīng)性能,即使應(yīng)用程序當(dāng)前正在完成其他工作。要使應(yīng)用程序能夠快速響應(yīng)用戶操作,同時(shí)在用戶事件之間或者甚至在用戶事件期間利用處理器,最強(qiáng)大的方式之一是使用多線程技術(shù)。

多線程:線程是程序中一個(gè)單一的順序控制流程.在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程。如果某個(gè)線程進(jìn)行一次長延遲操作, 處理器就切換到另一個(gè)線程執(zhí)行。這樣,多個(gè)線程的并行(并發(fā))執(zhí)行隱藏了長延遲,提高了處理器資源利用率,從而提高了整體性能。多線程是為了同步完成多項(xiàng)任務(wù),不是為了提高運(yùn)行效率,而是為了提高資源使用效率來提高系統(tǒng)的效率

一、進(jìn)程與線程

進(jìn)程,是操作系統(tǒng)進(jìn)行資源調(diào)度和分配的基本單位。是由進(jìn)程控制塊、程序段、數(shù)據(jù)段三部分組成。一個(gè)進(jìn)程可以包含若干線程(Thread),線程可以幫助應(yīng)用程序同時(shí)做幾件事(比 如一個(gè)線程向磁盤寫入文件,另一個(gè)則接收用戶的按鍵操作并及時(shí)做出反應(yīng),互相不干擾),在程序被運(yùn)行后中,系統(tǒng)首先要做的就是為該程序進(jìn)程建立一個(gè)默認(rèn)線程,然后程序可 以根據(jù)需要自行添加或刪除相關(guān)的線程。它是可并發(fā)執(zhí)行的程序。在一個(gè)數(shù)據(jù)集合上的運(yùn)行過程,是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,也是稱活動(dòng)、路徑或任務(wù),它有兩方面性質(zhì):活動(dòng)性、并發(fā)性。進(jìn)程可以劃分為運(yùn)行、阻塞、就緒三種狀態(tài),并隨一定條件而相互轉(zhuǎn)化:就緒--運(yùn)行,運(yùn)行--阻塞,阻塞--就緒。

線程(thread),線程是CPU調(diào)度和執(zhí)行的最小單位。有時(shí)被稱為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。由于線程之間的相互制約,致使線程在運(yùn)行中呈現(xiàn)出間斷性。線程也有就緒、阻塞和運(yùn)行三種基本狀態(tài)。

主線程,進(jìn)程創(chuàng)建時(shí),默認(rèn)創(chuàng)建一個(gè)線程,這個(gè)線程就是主線程。主線程是產(chǎn)生其他子線程的線程,同時(shí),主線程必須是最后一個(gè)結(jié)束執(zhí)行的線程,它完成各種關(guān)閉其他子線程的操作。盡管主線程是程序開始時(shí)自動(dòng)創(chuàng)建的,它也可以通過Thead類對(duì)象來控制,通過調(diào)用CurrentThread方法獲得當(dāng)前線程的引用

多線程的優(yōu)勢(shì):進(jìn)程有獨(dú)立的地址空間,同一進(jìn)程內(nèi)的線程共享進(jìn)程的地址空間。啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。

二、多線程優(yōu)點(diǎn)

1、提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
2、使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
3、改善程序結(jié)構(gòu)。一個(gè)既長又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。。

多線程盡管優(yōu)勢(shì)明顯,但是線程并發(fā)沖突、同步以及管理跟蹤,可能給系統(tǒng)帶來很多不確定性,這些必須引起足夠重視。

廢話不多說開始我們的多線程之旅。

三、多線程的應(yīng)用場(chǎng)合:

簡單總結(jié)了一下,一般有兩種情況:

1)多個(gè)線程,完成同類任務(wù),提高并發(fā)性能

2)一個(gè)任務(wù)有多個(gè)獨(dú)立的步驟,多個(gè)線程并發(fā)執(zhí)行各子任務(wù),提高任務(wù)處理效率

四、案例--搬運(yùn)工

在我們現(xiàn)實(shí)生活中,經(jīng)常看到這樣的場(chǎng)景。有一堆貨物,有幾個(gè)搬運(yùn)工負(fù)責(zé)將貨物搬運(yùn)到指定地點(diǎn)。但是搬運(yùn)工能力不同,有人一次能搬多箱,有人走路比較慢,搬運(yùn)一趟的時(shí)間間隔比較長。搬運(yùn)工,各自搬運(yùn),無先后,互不干擾。我們?nèi)绾卧诔绦蛑袑?shí)現(xiàn)這種場(chǎng)景呢?

案例分析:
這個(gè)就是最簡單的多線程的實(shí)際案例。每個(gè)人相當(dāng)于一個(gè)線程,并發(fā)執(zhí)行。當(dāng)貨物搬運(yùn)完畢后,每個(gè)線程自動(dòng)停止。這里暫時(shí)不考慮死鎖情況。

案例代碼:

復(fù)制代碼 代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MutiThreadSample.Transport
{
    /// <summary>
    /// 搬運(yùn)工
    /// </summary>
    public class Mover
    {
        /// <summary>
        /// 總數(shù)
        /// </summary>
        public static int GoodsTotal { get; set; }
        /// <summary>
        /// 間隔時(shí)間
        /// </summary>
        public static int IntervalTime { get; set; }
        /// <summary>
        /// 名稱
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 單位時(shí)間搬運(yùn)量
        /// </summary>
        public int LaborAmount { get; set; }
        /// <summary>
        /// 搬運(yùn)
        /// </summary>
        public void Move()
        {
            while (GoodsTotal > 0)
            {
                GoodsTotal -= LaborAmount;
                Console.WriteLine("搬運(yùn)者:{0} 于 {1} 搬運(yùn)貨物 {2}",this.Name,DateTime.Now.Millisecond,this.LaborAmount);
                Thread.Sleep(IntervalTime);
                Console.WriteLine("搬運(yùn)者:{0} Continue",this.Name);
            }
        }
        /// <summary>
        /// 搬運(yùn)
        /// </summary>
        /// <param name="interval">時(shí)間間隔</param>
        public void Move(object interval)
        {
            int tempInterval = 0;
            if (!int.TryParse(interval.ToString(), out tempInterval))
            {
                tempInterval = IntervalTime;
            }           
            while (GoodsTotal > 0)
            {
                GoodsTotal -= LaborAmount;
                Console.WriteLine("搬運(yùn)者:{0} 于 {1} 搬運(yùn)貨物 {2}", this.Name, DateTime.Now.Millisecond, this.LaborAmount);
                Thread.Sleep(tempInterval);
            }
        }
     }
}

測(cè)試:

復(fù)制代碼 代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MutiThreadSample.Transport
{
    /// <summary>
    /// 測(cè)試搬運(yùn)
    /// </summary>
    public class TestMove
    {
        /// <summary>
        /// 搬運(yùn)
        /// </summary>
        public static void Move()
        {
            //測(cè)試搬運(yùn)工
            Mover.GoodsTotal = 200;
            Mover.IntervalTime = 10;
            Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 };
            Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 };
            Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 };

            List<Mover> movers = new List<Mover>();
            movers.Add(m1);
            //movers.Add(m2);
            //movers.Add(m3);

            if (movers != null && movers.Count > 0)
            {
                foreach (Mover m in movers)
                {                   
                    Thread thread = new Thread(new ThreadStart(m.Move));
                    thread.Start();
                }
            }

            //Main Thread continue
            // validate Thread.Sleep()
            //int i =0;
            //int j = 0;
            //while (i < 10)
            //{
            //    while(j<10000000)
            //    {
            //        j++;               
            //    }
            //    Console.WriteLine("CurrentThread:{0}", Thread.CurrentThread.Name);
            //    i++;
            //}

        }

        /// <summary>
        /// 搬運(yùn)
        /// </summary>
        public static void MoveWithParamThread()
        {
            //測(cè)試搬運(yùn)工
            Mover.GoodsTotal = 1000;
            Mover.IntervalTime = 100;
            Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 };
            Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 };
            Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 };

            List<Mover> movers = new List<Mover>();
            movers.Add(m1);
            movers.Add(m2);
            movers.Add(m3);

            if (movers != null && movers.Count > 0)
            {
                foreach (Mover m in movers)
                {
                    Thread thread = new Thread(new ParameterizedThreadStart(m.Move));
                    thread.Start(10);
                }
            }
        }
    }
}

 通過案例我們也接觸了Thread,下面我們將詳細(xì)介紹Thread的功能。

五、Thread

 創(chuàng)建并控制線程,設(shè)置其優(yōu)先級(jí)并獲取其狀態(tài)。

常用方法:

Start()

導(dǎo)致操作系統(tǒng)將當(dāng)前實(shí)例的狀態(tài)更改為 ThreadState.Running。

一旦線程處于 ThreadState.Running 狀態(tài),操作系統(tǒng)就可以安排其執(zhí)行。 線程從方法的第一行(由提供給線程構(gòu)造函數(shù)的 ThreadStart 或 ParameterizedThreadStart 委托表示)開始執(zhí)行。線程一旦終止,它就無法通過再次調(diào)用 Start 來重新啟動(dòng)。

Thread.Sleep()

調(diào)用 Thread.Sleep 方法會(huì)導(dǎo)致當(dāng)前線程立即阻止,阻止時(shí)間的長度等于傳遞給 Thread.Sleep 的毫秒數(shù),這樣,就會(huì)將其時(shí)間片中剩余的部分讓與另一個(gè)線程。 一個(gè)線程不能針對(duì)另一個(gè)線程調(diào)用 Thread.Sleep。

Interrupt()
中斷處于 WaitSleepJoin 線程狀態(tài)的線程。

Suspend和Resume(已過時(shí))
掛起和繼續(xù)
在 .NET Framework 2.0 版中,Thread.Suspend 和 Thread.Resume 方法已標(biāo)記為過時(shí),并將從未來版本中移除。

Abort()
方法用于永久地停止托管線程。一旦線程被中止,它將無法重新啟動(dòng)。

Join()
阻塞調(diào)用線程,直到某個(gè)線程終止時(shí)為止。

ThreadPriority(優(yōu)先級(jí))
指定 Thread 的調(diào)度優(yōu)先級(jí)。
ThreadPriority 定義一組線程優(yōu)先級(jí)的所有可能值。線程優(yōu)先級(jí)指定一個(gè)線程相對(duì)于另一個(gè)線程的相對(duì)優(yōu)先級(jí)。
每個(gè)線程都有一個(gè)分配的優(yōu)先級(jí)。在運(yùn)行庫內(nèi)創(chuàng)建的線程最初被分配 Normal 優(yōu)先級(jí),而在運(yùn)行庫外創(chuàng)建的線程在進(jìn)入運(yùn)行庫時(shí)將保留其先前的優(yōu)先級(jí)。可以通過訪問線程的 Priority 屬性來獲取和設(shè)置其優(yōu)先級(jí)。
根據(jù)線程的優(yōu)先級(jí)調(diào)度線程的執(zhí)行。用于確定線程執(zhí)行順序的調(diào)度算法隨操作系統(tǒng)的不同而不同。操作系統(tǒng)也可以在用戶界面的焦點(diǎn)在前臺(tái)和后臺(tái)之間移動(dòng)時(shí)動(dòng)態(tài)地調(diào)整線程的優(yōu)先級(jí)。
一個(gè)線程的優(yōu)先級(jí)不影響該線程的狀態(tài);該線程的狀態(tài)在操作系統(tǒng)可以調(diào)度該線程之前必須為 Running。

六、創(chuàng)建線程方式

通過搬運(yùn)工案例我們能夠了解線程的工作原理,也明白了線程的創(chuàng)建方式。

其實(shí)在C#中創(chuàng)建線程有幾種方式,這里給大家舉幾個(gè)常用例子,如下:

復(fù)制代碼 代碼如下:

using System;
using System.Threading;

namespace MutiThreadSample
{
    /// <summary>
    /// 創(chuàng)建線程的方式
    /// </summary>
    class CreateThread
    {
        /// <summary>
        /// 不帶參數(shù)的委托
        /// </summary>
        public void CreateThreadWithThreadStart()
        {
            Thread thread = new Thread(new ThreadStart(ThreadCallBack));
            thread.Start();
        }
        /// <summary>
        /// 帶參數(shù)的委托
        /// </summary>
        public void CreateThreadWithParamThreadStart()
        {
            Thread thread = new Thread(new ParameterizedThreadStart(ThreadCallBackWithParam));
            thread.Start();
        }
        /// <summary>
        /// 匿名函數(shù)
        /// </summary>
        public void CreateThreadWithAnonymousFunction()
        {
            Thread thread = new Thread(delegate()
            {
                Console.WriteLine("進(jìn)入子線程1");
                for (int i = 1; i < 4; ++i)
                {
                    Thread.Sleep(50);
                    Console.WriteLine("/t+++++++子線程1+++++++++");
                }
                Console.WriteLine("退出子線程1");
            });
            thread.Start();
        }
        /// <summary>
        /// 直接賦值委托
        /// </summary>
        public void CreateThreadWithCallBack()
        {
            Thread _hThread = new Thread(ThreadCallBack);
            _hThread.Start();

        }
        /// <summary>
        /// 無參數(shù)的方法調(diào)用
        /// </summary>
        public void ThreadCallBack()
        {
            // Do Something
        }
        /// <summary>
        /// 帶參數(shù)的方法
        /// </summary>
        /// <param name="obj"></param>
        public void ThreadCallBackWithParam(object obj)
        {
            // Do Something
        } 
    }
}

時(shí)鐘線程

使用 TimerCallback 委托指定希望 Timer 執(zhí)行的方法。 計(jì)時(shí)器委托在構(gòu)造計(jì)時(shí)器時(shí)指定,并且不能更改。 此方法不在創(chuàng)建計(jì)時(shí)器的線程上執(zhí)行,而是在系統(tǒng)提供的 ThreadPool 線程上執(zhí)行。創(chuàng)建計(jì)時(shí)器時(shí),可以指定在第一次執(zhí)行方法之前等待的時(shí)間量(截止時(shí)間)以及此后的執(zhí)行期間等待的時(shí)間量(時(shí)間周期)。 可以使用 Change 方法更改這些值或禁用計(jì)時(shí)器。

復(fù)制代碼 代碼如下:

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        // Create an event to signal the timeout count threshold in the
        // timer callback.
        AutoResetEvent autoEvent     = new AutoResetEvent(false);

        StatusChecker  statusChecker = new StatusChecker(10);

        // Create an inferred delegate that invokes methods for the timer.
        TimerCallback tcb = statusChecker.CheckStatus;

        // Create a timer that signals the delegate to invoke
        // CheckStatus after one second, and every 1/4 second
        // thereafter.
        Console.WriteLine("{0} Creating timer./n",
            DateTime.Now.ToString("h:mm:ss.fff"));
        Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every
        // 1/2 second.
        autoEvent.WaitOne(5000, false);
        stateTimer.Change(0, 500);
        Console.WriteLine("/nChanging period./n");

        // When autoEvent signals the second time, dispose of
        // the timer.
        autoEvent.WaitOne(5000, false);
        stateTimer.Dispose();
        Console.WriteLine("/nDestroying timer.");
    }
}

class StatusChecker
{
    private int invokeCount;
    private int  maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.",
            DateTime.Now.ToString("h:mm:ss.fff"),
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal Main.
            invokeCount  = 0;
            autoEvent.Set();
        }
    }
}



七、前臺(tái)線程和后臺(tái)線程

.Net的公用語言運(yùn)行時(shí)(Common Language Runtime,CLR)能區(qū)分兩種不同類型的線程:前臺(tái)線程和后臺(tái)線程。這兩者的區(qū)別就是:應(yīng)用程序必須運(yùn)行完所有的前臺(tái)線程才可以退出;而對(duì)于后臺(tái)線程,應(yīng)用程序則可以不考慮其是否已經(jīng)運(yùn)行完畢而直接退出,所有的后臺(tái)線程在應(yīng)用程序退出時(shí)都會(huì)自動(dòng)結(jié)束。

一個(gè)線程是前臺(tái)線程還是后臺(tái)線程可由它的IsBackground屬性來決定。這個(gè)屬性是可讀又可寫的。它的默認(rèn)值為false,即意味著一個(gè)線程默認(rèn)為前臺(tái)線程。

我們可以將它的IsBackground屬性設(shè)置為true,從而使之成為一個(gè)后臺(tái)線程。下面的例子是一個(gè)控制臺(tái)程序,程序一開始便啟動(dòng)了10個(gè)線程,每個(gè)線程運(yùn)行5秒鐘時(shí)間。由于線程的IsBackground屬性默認(rèn)為false,即它們都是前臺(tái)線程,所以盡管程序的主線程很快就運(yùn)行結(jié)束了,但程序要到所有已啟動(dòng)的線程都運(yùn)行完畢才會(huì)結(jié)束。示例代碼如下例子中的Test()所示

復(fù)制代碼 代碼如下:

using System;
using System.Threading;

namespace MutiThreadSample.ThreadType
{
    class ThreadTypeTest
    {
        /// <summary>
        /// 測(cè)試前臺(tái)線程
        /// </summary>
        public static void Test()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(new ThreadStart(ThreadFunc));
                thread.Start();
            }
        }
        /// <summary>
        /// 測(cè)試后臺(tái)線程
        /// </summary>
        public static void TestBackgroundThread()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread thread = new Thread(new ThreadStart(ThreadFunc));
                thread.IsBackground = true;
                thread.Start();
            }
        }
        public static void ThreadFunc()
        {
            Thread.Sleep(0);
            DateTime start = DateTime.Now;
            while ((DateTime.Now - start).Seconds < 20);//可以停頓的時(shí)間長一點(diǎn),效果更加明顯
        }
    }
}

接下來我們對(duì)上面的代碼進(jìn)行略微修改,將每個(gè)線程的IsBackground屬性都設(shè)置為true,則每個(gè)線程都是后臺(tái)線程了。那么只要程序的主線程結(jié)束了,整個(gè)程序也就結(jié)束了。示例代碼如代碼中的TestBackgroundThread()。

這個(gè)例子直接創(chuàng)建一個(gè)控制臺(tái)程序即可檢驗(yàn)。

前臺(tái)和后臺(tái)線程的使用原則

既然前臺(tái)線程和后臺(tái)線程有這種差別,那么我們?cè)趺粗涝撊绾卧O(shè)置一個(gè)線程的IsBackground屬性呢?下面是一些基本的原則:對(duì)于一些在后臺(tái)運(yùn)行的線程,當(dāng)程序結(jié)束時(shí)這些線程沒有必要繼續(xù)運(yùn)行了,那么這些線程就應(yīng)該設(shè)置為后臺(tái)線程。比如一個(gè)程序啟動(dòng)了一個(gè)進(jìn)行大量運(yùn)算的線程,可是只要程序一旦結(jié)束,那個(gè)線程就失去了繼續(xù)存在的意義,那么那個(gè)線程就該是作為后臺(tái)線程的。而對(duì)于一些服務(wù)于用戶界面的線程往往是要設(shè)置為前臺(tái)線程的,因?yàn)榧词钩绦虻闹骶€程結(jié)束了,其他的用戶界面的線程很可能要繼續(xù)存在來顯示相關(guān)的信息,所以不能立即終止它們。這里我只是給出了一些原則,具體到實(shí)際的運(yùn)用往往需要編程者的進(jìn)一步仔細(xì)斟酌。 

八、總結(jié)

這一章主要介紹多線程技術(shù)的基本知識(shí)。涉及多線程的具體應(yīng)用,包括預(yù)防死鎖、線程同步、線程池等,在今后的文章會(huì)涉及到。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 国产精品久久免费看 | 热久久这里只有精品 | 亚洲一区 | 国产精品毛片久久久久久久av | 日韩av资源站 | 一级免费毛片 | 激情六月婷| 久久久久国产一区二区三区四区 | 国产中文字幕一区二区 | 欧美成人精品一区二区男人看 | 综合精品久久久 | 精品国产91乱码一区二区三区 | 日韩精品在线观看免费 | 日韩免费福利视频 | 在线天堂新版最新版在线8 久久亚洲欧美日韩精品专区 | 国产精品视频久久久 | 三级网站| 亚洲人成网亚洲欧洲无码 | 久久午夜夜伦鲁鲁一区二区 | 欧美天堂在线观看 | 国产一区在线看 | 国产精品丝袜一区二区 | 精品国产91亚洲一区二区三区www | 91精品久久久久久久久 | 久久国产精品精品国产 | 欧美成人h版在线观看 | 亚洲午夜视频 | 久久首页 | 久精品视频 | 亚洲男人天堂2023 | 97电影在线观看 | 国产精品国产 | 97男人的天堂 | 影音先锋亚洲资源 | 97精品一区 | 亚洲综合在线一区 | 五月婷婷免费视频 | 亚洲精品一区二区网址 | 国产一区二区三区久久久 | 日本三级在线 | 日韩精品免费一区二区夜夜嗨 |