欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#多線程之線程鎖

 更新時(shí)間:2022年05月13日 10:32:11   作者:springsnow  
這篇文章介紹了C#多線程中的線程鎖,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、Mutex類

“mutex”是術(shù)語“互相排斥(mutually exclusive)”的簡(jiǎn)寫形式,也就是互斥量?;コ饬扛R界區(qū)中提到的Monitor很相似,只有擁有互斥對(duì)象的線程才具有訪問資源的權(quán)限,由于互斥對(duì)象只有一個(gè),因此就決定了任何情況下此共享資源都不會(huì)同時(shí)被多個(gè)線程所訪問。當(dāng)前占據(jù)資源的線程在任務(wù)處理完后應(yīng)將擁有的互斥對(duì)象交出,以便其他線程在獲得后得以訪問資源。互斥量比臨界區(qū)復(fù)雜,因?yàn)槭褂没コ獠粌H僅能夠在同一應(yīng)用程序不同線程中實(shí)現(xiàn)資源的安全共享,而且可以在不同應(yīng)用程序的線程之間實(shí)現(xiàn)對(duì)資源的安全共享。.Net中mutex由Mutex類來表示。

二、Mutex的用途

Mutex并不適合于有相互消息通知的同步;另一方面局部Mutex應(yīng)該被Monitor/lock所取代;而跨應(yīng)用程序的、相互消息通知的同步由EventWaiteHandle/AutoResetEvent/ManualResetEvent承擔(dān)更合適。所以,Mutex在.net中應(yīng)用的場(chǎng)景似乎不多。不過,Mutex有個(gè)最常見的用途:用于控制一個(gè)應(yīng)用程序只能有一個(gè)實(shí)例運(yùn)行。系統(tǒng)依靠這個(gè)name屬性來標(biāo)識(shí)唯一的Mutex。

private static Mutex mutex = null;  //設(shè)為Static成員,是為了在整個(gè)程序生命周期內(nèi)持有Mutex

static void Main()
{
    bool firstInstance;

    mutex = new Mutex(true, @"Global\MutexSampleApp", out firstInstance);
    try
    {
        if (firstInstance)
        {
            Console.WriteLine("我們是第一個(gè)實(shí)例!");
        }
        else
        {
            Console.WriteLine("警告,已有實(shí)例運(yùn)行!");
            return;
        }
    }
    finally
    {
        //只有第一個(gè)實(shí)例獲得控制權(quán),因此只有在這種情況下才需要ReleaseMutex,否則會(huì)引發(fā)異常。
        if (firstInstance)
        {
            mutex.ReleaseMutex();
        }
        mutex.Close();
        mutex = null;
    }
}

三、Semaphore信號(hào)量

1、簡(jiǎn)介

Semaphore是操作系統(tǒng)中用于控制線程同步互斥的信號(hào)量。在編寫多線程的程序時(shí),可以使用Semaphore信號(hào)量來協(xié)調(diào)多線程并行,使各個(gè)線程能夠合理地共享資源,保證程序正確運(yùn)行。

2、初始化

初始化Semaphore可當(dāng)做開啟了一個(gè)線程池,initialCount代表剩余空位,maximumCount代表最大容量。示例如下,當(dāng)前空位為0,最大容量為1:

Semaphore sem = new Semaphore(0, 1);

3、WaitOne()和Release()

Semaphore常用的方法有兩個(gè)WaitOne()和Release()。

使用WaitOne()方法相當(dāng)于等待出現(xiàn)退出的線程,而使用Release()方法為讓一個(gè)線程退出。

假設(shè)initialCount和maximumCount都為5,開始的時(shí)候線程池有5個(gè)空位置,且總共只有5個(gè)位置,當(dāng)需要并行的線程數(shù)量超過5個(gè)時(shí),首先使用WaitOne()方法等待,發(fā)現(xiàn)有空位就依次進(jìn)去,每進(jìn)去一個(gè)空位減1,直到進(jìn)去5個(gè)線程之后,空位(initialCount)為0,這時(shí)候后面的線程就一直等待,直到有線程調(diào)用了Release()方法,主動(dòng)退出線程池,空位加1,在等待的線程才能繼續(xù)進(jìn)入線程池。

下面的代碼示例創(chuàng)建一個(gè)信號(hào)量, 其最大計(jì)數(shù)為 3, 初始計(jì)數(shù)為零。 該示例啟動(dòng)五個(gè)線程, 這會(huì)阻止等待信號(hào)量。 主線程使用Release(Int32)方法重載將信號(hào)量計(jì)數(shù)增加到其最大值, 從而允許三個(gè)線程進(jìn)入信號(hào)量。 每個(gè)線程使用Thread.Sleep方法等待一秒, 以模擬工作, 然后Release()調(diào)用方法重載以釋放信號(hào)量。 每次釋放信號(hào)燈時(shí), 都將顯示以前的信號(hào)量計(jì)數(shù)。 控制臺(tái)消息跟蹤信號(hào)量使用。 每個(gè)線程的模擬工作時(shí)間間隔略有增加, 使輸出更易于讀取。

private static Semaphore _pool;

private static int _padding;

public static void Main()
{
    _pool = new Semaphore(0, 3);

    for (int i = 1; i <= 5; i++)//創(chuàng)建并啟動(dòng)五個(gè)線程。
    {
        Thread t = new Thread(new ParameterizedThreadStart(Worker));

        t.Start(i);// 啟動(dòng)線程,傳遞數(shù)字。
    }

    Thread.Sleep(500);

    Console.WriteLine("Main thread calls Release(3).");
    _pool.Release(3);//調(diào)用Release(3)會(huì)使信號(hào)量計(jì)數(shù)回其最大值,并允許等待的線程進(jìn)入信號(hào)量,一次最多三個(gè)。

    Console.WriteLine("Main thread exits.");
}

private static void Worker(object num)
{
    Console.WriteLine("Thread {0} begins " + "and waits for the semaphore.", num);
    _pool.WaitOne();

    //填充間隔,使輸出更加有序。
    int padding = Interlocked.Add(ref _padding, 100);

    Console.WriteLine("Thread {0} enters the semaphore.", num);

    Thread.Sleep(1000 + padding);

    Console.WriteLine("Thread {0} releases the semaphore.", num);
    Console.WriteLine("Thread {0} previous semaphore count: {1}", num, _pool.Release());
}

四、Monitor類

當(dāng)多個(gè)線程公用一個(gè)對(duì)象時(shí),也會(huì)出現(xiàn)和公用代碼類似的問題,這就需要用到 System.Threading 中的 Monitor 類,我們可以稱之為監(jiān)視器,Monitor 提供了使線程共享資源的方案。
Monitor 類可以鎖定一個(gè)對(duì)象,一個(gè)線程只有得到這把鎖才可以對(duì)該對(duì)象進(jìn)行操作。 對(duì)象鎖機(jī)制保證了在可能引起混亂的情況下,一個(gè)時(shí)刻只有一個(gè)線程可以訪問這個(gè)對(duì)象。Monitor 必須和一個(gè)具體的對(duì)象相關(guān)聯(lián)。

下面代碼說明了使用 Monitor 鎖定一個(gè)對(duì)象的情形:

// 表示對(duì)象的先進(jìn)先出集合
Queue oQueue = new Queue();
try
{
    // 現(xiàn)在 oQueue 對(duì)象只能被當(dāng)前線程操縱了
    Monitor.Enter(oQueue);
 
    // do something......
}
catch
{
 
}
finally
{
    // 釋放鎖 
    Monitor.Exit(oQueue);
}

如上所示, 當(dāng)一個(gè)線程調(diào)用 Monitor.Enter() 方法鎖定一個(gè)對(duì)象時(shí),這個(gè)對(duì)象就歸它所有了,其它線程想要訪問這個(gè)對(duì)象,只有等待它使用Monitor.Exit() 方法釋放鎖。為了保證線程最終都能釋放鎖,你可以把 Monitor.Exit() 方法寫在 try-catch-finally 結(jié)構(gòu)中的 finally 代碼塊里。(lock 關(guān)鍵字就是這個(gè)步驟的語法糖

 任何一個(gè)被 Monitor 鎖定的對(duì)象,內(nèi)存中都保存著與它相關(guān)的一些信息:

  • 現(xiàn)在持有鎖的線程的引用
  • 一個(gè)預(yù)備隊(duì)列,隊(duì)列中保存了已經(jīng)準(zhǔn)備好獲取鎖的線程
  • 一個(gè)等待隊(duì)列,隊(duì)列中保存著當(dāng)前正在等待這個(gè)對(duì)象狀態(tài)改變的隊(duì)列的引用

當(dāng)擁有對(duì)象鎖的線程準(zhǔn)備釋放鎖時(shí),它使用 Monitor.Pulse() 方法通知等待隊(duì)列中的第一個(gè)線程,于是該線程被轉(zhuǎn)移到預(yù)備隊(duì)列中,當(dāng)對(duì)象鎖被釋放時(shí),在預(yù)備隊(duì)列中的線程可以立即獲得對(duì)象鎖。

典型的生產(chǎn)者與消費(fèi)者實(shí)例

下面是一個(gè)展示如何使用 lock 關(guān)鍵字和 Monitor 類來實(shí)現(xiàn)線程的同步和通訊的例子。在本例中,生產(chǎn)者線程和消費(fèi)者線程是交替進(jìn)行的,生產(chǎn)者寫入一個(gè)數(shù),消費(fèi)者立即讀取并且顯示(注釋中介紹了該程序的精要所在)。

/// <summary>
/// 測(cè)試類
/// </summary>
public class MonitorSample
{
    public static void Main(String[] args)
    {
        // 一個(gè)標(biāo)志位,如果是 0 表示程序沒有出錯(cuò),如果是 1 表明有錯(cuò)誤發(fā)生
        int result = 0;

        // 下面使用 cell 初始化 CellProd 和 CellCons 兩個(gè)類,生產(chǎn)和消費(fèi)次數(shù)均為 20 次 
        Cell cell = new Cell();
        CellProd prod = new CellProd(cell, 20);
        CellCons cons = new CellCons(cell, 20);
        Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
        Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));

        // 生產(chǎn)者線程和消費(fèi)者線程都已經(jīng)被創(chuàng)建,但是沒有開始執(zhí)行 
        try
        {
            producer.Start();
            consumer.Start();
            producer.Join();
            consumer.Join();//等待這兩個(gè)線程結(jié)束才往下執(zhí)行
            Console.ReadLine();
        }
        catch (ThreadStateException e)
        {
            // 當(dāng)線程因?yàn)樗帬顟B(tài)的原因而不能執(zhí)行被請(qǐng)求的操作 
            Console.WriteLine(e);
            result = 1;
        }
        catch (ThreadInterruptedException e)
        {
            // 當(dāng)線程在等待狀態(tài)的時(shí)候中止 
            Console.WriteLine(e);
            result = 1;
        }
        // 盡管 Main() 函數(shù)沒有返回值,但下面這條語句可以向父進(jìn)程返回執(zhí)行結(jié)果 
        Environment.ExitCode = result;
    }
}

/// <summary>
/// 生產(chǎn)者
/// </summary>
public class CellProd
{
    /// <summary>
    /// 被操作的 Cell 對(duì)象
    /// </summary>
    Cell cell;

    /// <summary>
    /// 生產(chǎn)者生產(chǎn)次數(shù),初始化為 1 
    /// </summary>
    int quantity = 1;

    public CellProd(Cell box, int request)
    {
        cell = box;
        quantity = request;
    }

    public void ThreadRun()
    {
        for (int looper = 1; looper <= quantity; looper++)
        {
            // 生產(chǎn)者向操作對(duì)象寫入信息 
            cell.WriteToCell(looper);
        }
    }
}

/// <summary>
/// 消費(fèi)者
/// </summary>
public class CellCons
{
    Cell cell;
    int quantity = 1;

    public CellCons(Cell box, int request)
    {
        cell = box;
        quantity = request;
    }

    public void ThreadRun()
    {
        int valReturned;
        for (int looper = 1; looper <= quantity; looper++)
        {
            valReturned = cell.ReadFromCell(); // 消費(fèi)者從操作對(duì)象中讀取信息 
        }
    }
}

/// <summary>
/// 被操作的對(duì)象
/// </summary>
public class Cell
{
    /// <summary>
    /// Cell 對(duì)象里的內(nèi)容
    /// </summary>
    int cellContents;

    /// <summary>
    /// 狀態(tài)標(biāo)志: 為 true 時(shí)可以讀取,為 false 則正在寫入
    /// </summary>
    bool readerFlag = false;

    public int ReadFromCell()
    {
        lock (this)
        {
            if (!readerFlag)
            {
                try
                {
                    // 等待 WriteToCell 方法中調(diào)用 Monitor.Pulse()方法 
                    Monitor.Wait(this);
                }
                catch (SynchronizationLockException e)
                {
                    Console.WriteLine(e);
                }
                catch (ThreadInterruptedException e)
                {
                    Console.WriteLine(e);
                }
            }

            // 開始消費(fèi)行為
            Console.WriteLine("Consume: {0}", cellContents);
            Console.WriteLine();

            // 重置 readerFlag 標(biāo)志,表示消費(fèi)行為已經(jīng)完成 
            readerFlag = false;

            Monitor.Pulse(this);// 通知 WriteToCell()方法(該方法在另外一個(gè)線程中執(zhí)行,等待中)
        }
        return cellContents;
    }

    public void WriteToCell(int n)
    {
        lock (this)
        {
            if (readerFlag)
            {
                try
                {
                    Monitor.Wait(this);
                }
                catch (SynchronizationLockException e)
                {
                    // 當(dāng)同步方法(指Monitor類除Enter之外的方法)在非同步的代碼區(qū)被調(diào)用 
                    Console.WriteLine(e);
                }
                catch (ThreadInterruptedException e)
                {
                    // 當(dāng)線程在等待狀態(tài)的時(shí)候中止 
                    Console.WriteLine(e);
                }
            }
            cellContents = n;
            Console.WriteLine("Produce: {0}", cellContents);
            readerFlag = true;
            Monitor.Pulse(this); // 通知另外一個(gè)線程中正在等待的 ReadFromCell() 方法 
        }
    }
}

五、Lock

C# 提供了一個(gè)關(guān)鍵字 lock,它可以把一段代碼定義為互斥段(critical section),互斥段在一個(gè)時(shí)刻內(nèi)只允許一個(gè)線程進(jìn)入執(zhí)行,而其他線程必須等待。

在C#中,關(guān)鍵字 lock 的定義:

lock(expression) 
 {statement_block}

expression 代表你希望跟蹤的對(duì)象,通常是對(duì)象引用。如果你想保護(hù)一個(gè)類的實(shí)例,你可以使用 this;如果你想保護(hù)一個(gè)靜態(tài)變量(如互斥代碼段在一個(gè)靜態(tài)方法內(nèi)部),一般使用鎖定一個(gè)私有的static 成員變量就可以了。而 statement_block 就是互斥段的代碼,這段代碼在一個(gè)時(shí)刻內(nèi)只可能被一個(gè)線程執(zhí)行。

NET在一些集合類中(比如ArrayList,HashTable,Queue,Stack)已經(jīng)提供了一個(gè)供lock使用的對(duì)象SyncRoot,用Reflector工具查看了SyncRoot屬性的代碼,在Array中,該屬性只有一句話:return this,這樣和lock array的當(dāng)前實(shí)例是一樣的。

Lock 語法簡(jiǎn)單易用。其本質(zhì)是針對(duì) Monitor.Enter() 和 Monitor.Exit() 的封裝,是一個(gè)語法糖!

static internal Thread[] threads = new Thread[10];
public static void Main()
{
    Account acc = new Account(100);
    for (int i = 0; i < 10; i++)
    {
        Thread t = new Thread(new ThreadStart(acc.DoTransactions));
        threads[i] = t;
        threads[i].Name = i.ToString();
        threads[i].Start(); //10個(gè)線程同時(shí)啟動(dòng)
    }
}

internal class Account
{
    int balance;internal Account(int initial)
    {
        balance = initial;
    }

    internal void DoTransactions()
    {
        for (int i = 0; i < 100; i++)
        {
            int amount = new Random().Next(-100, 100);

            lock (this)
            {
                Console.WriteLine("當(dāng)前線程:" + Thread.CurrentThread.Name + " 余額:" + balance.ToString() + " 數(shù)量:" + amount);
                if (balance >= amount)
                {
                    Thread.Sleep(5);
                    balance = balance - amount;
                }
                else
                {
                    Console.WriteLine("當(dāng)前線程:" + Thread.CurrentThread.Name + " 不能交易,余額不足:" + balance.ToString());
                    return;
                }
            }
        }
    }
}

六、InterLocked(相當(dāng)于lock,對(duì)整數(shù))

在C#中,賦值和簡(jiǎn)單的數(shù)字運(yùn)算都不是原子型操作。 在多線程環(huán)境下,我們可以通過使用System.Threading.Interlocked類來實(shí)現(xiàn)原子型操作當(dāng)個(gè)數(shù)據(jù),使用它比使用Monitor類跟簡(jiǎn)單。

使用.NET提供的Interlocked類可以對(duì)一些數(shù)據(jù)進(jìn)行原子操作,看起來似乎跟lock鎖一樣,但它并不是lock鎖,它的原子操作是基于CPU本身的,非阻塞的,所以要比lock的效率高。

1、Interlocked類主要方法

  • Read() 安全讀取數(shù)值,相等于int a=b
  • Add() 安全相加一個(gè)數(shù)值,相當(dāng)于 a = a + 3
  • Increment() 安全遞加1,相當(dāng)于 i++。返回遞增后的值。
  • Decrement()安全遞減1,相當(dāng)于 i--Exchange() 安全交換數(shù)據(jù),返回遞減后的值。
  • CompareExchange() 安全比較兩個(gè)值是不是相等。如果相等,將第三個(gè)值與其中一個(gè)值交換。

2、實(shí)例

例一:

void Main()
{
    TestIncrementUnSafe();
    TestIncrementSafe();
}

private int value1 = 0;
public void TestIncrementUnSafe()
{
    for (int i = 0; i < 5; i++)
    {
        Thread t = new Thread(IncrementValue1);
        t.Name = "t1 " + i;
        t.Start();
    }
    Thread.Sleep(2000);
    //value maybe 500000
    Console.WriteLine("value1 = " + value1);
}
private void IncrementValue1()
{
    for (int i = 0; i < 1000000; i++)
    {
        value1++;
    }
}


private int value2 = 0;
public void TestIncrementSafe()
{
    for (int i = 0; i < 5; i++)
    {
        Thread t = new Thread(IncrementValue2);
        t.Name = "t2 " + i;
        t.Start();
    }
    Thread.Sleep(2000);
    //value should be 500000
    Console.WriteLine("value2 = " + value2);
}

private void IncrementValue2()
{
    for (int i = 0; i < 1000000; i++)
    {
        Interlocked.Increment(ref value2);
    }
}

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

value1 = 4612592
value2 = 5000000

例二、

void Main()
{
    TestExchangeSafe();
    TestCompareExchangeSafe();
}
private int value3 = 0;
public void TestExchangeSafe()
{
    for (int i = 0; i < 5; i++)
    {
        Thread t = new Thread(ExchangeValue3);
        t.Name = "t2 " + i;
        t.Start();
    }
    Thread.Sleep(2000);
    //value should be 83
    Console.WriteLine("value3 = " + value3);
}
private void ExchangeValue3()
{
    Interlocked.Exchange(ref value3, 83);
}


private int value4 = 0;
public void TestCompareExchangeSafe()
{
    for (int i = 0; i < 5; i++)
    {
        Thread t = new Thread(ExchangeValue3);
        t.Name = "t2 " + i;
        t.Start();
    }
    Thread.Sleep(2000);
    //value should be 99 or 0
    Console.WriteLine("value4 = " + value4);
}
private void ExchangeValue4()
{
    //if value4=0, set value4=99
    Interlocked.CompareExchange(ref value4, 99, 0);
}

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

value3 = 83
value4 = 0

到此這篇關(guān)于C#多線程之線程鎖的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C#實(shí)現(xiàn)獲取系統(tǒng)目錄并以Tree樹叉顯示的方法

    C#實(shí)現(xiàn)獲取系統(tǒng)目錄并以Tree樹叉顯示的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)獲取系統(tǒng)目錄并以Tree樹叉顯示的方法,可以加深讀者對(duì)于C#下數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)方法的認(rèn)識(shí),需要的朋友可以參考下
    2014-07-07
  • C#中的Hashtable?類使用詳解

    C#中的Hashtable?類使用詳解

    這篇文章主要介紹了C#中的Hashtable?類使用詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • c# DataDirectory的用法

    c# DataDirectory的用法

    這篇文章主要介紹了c# DataDirectory的用法,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下
    2020-08-08
  • c#批量抓取免費(fèi)代理并且驗(yàn)證有效性的實(shí)戰(zhàn)教程

    c#批量抓取免費(fèi)代理并且驗(yàn)證有效性的實(shí)戰(zhàn)教程

    突破反爬蟲限制的方法之一就是多用幾個(gè)代理IP,下面這篇文章主要給大家介紹了關(guān)于利用c#批量抓取免費(fèi)代理并且驗(yàn)證有效性的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2018-07-07
  • C#中三種Timer計(jì)時(shí)器的詳細(xì)用法

    C#中三種Timer計(jì)時(shí)器的詳細(xì)用法

    這篇文章介紹了C#中三種Timer計(jì)時(shí)器的詳細(xì)用法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • C# Winform實(shí)現(xiàn)自定義漂亮的通知效果

    C# Winform實(shí)現(xiàn)自定義漂亮的通知效果

    這篇文章主要介紹了C# Winform實(shí)現(xiàn)自定義漂亮的通知效果,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-08-08
  • C#實(shí)現(xiàn)安全刪除文件目錄的方法

    C#實(shí)現(xiàn)安全刪除文件目錄的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)安全刪除文件目錄的方法,涉及C#刪除文件目錄的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-05-05
  • C#實(shí)現(xiàn)獲得某個(gè)枚舉的所有名稱

    C#實(shí)現(xiàn)獲得某個(gè)枚舉的所有名稱

    這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)獲得某個(gè)枚舉的所有名稱,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下
    2025-01-01
  • 詳解WPF中的APP生命周期以及全局異常捕獲

    詳解WPF中的APP生命周期以及全局異常捕獲

    這篇文章主要為大家詳細(xì)介紹了WPF中的APP生命周期以及全局異常捕獲的相關(guān)資料,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)WPF有一點(diǎn)的幫助,需要的可以了解一下
    2023-03-03
  • RabbitMQ的配置與安裝教程全紀(jì)錄

    RabbitMQ的配置與安裝教程全紀(jì)錄

    這篇文章主要給大家介紹了關(guān)于RabbitMQ的配置與安裝的相關(guān)資料,文中通過示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07

最新評(píng)論