簡體   English   中英

為什么可以在同一個類中創建的另一個線程中訪問局部變量?

[英]Why can a local variable be accessed in another thread created in the same class?

我真的找不到關於這個確切主題的任何內容,所以如果問題已經存在,請引導我走向正確的方向。

根據我對.NET的了解,不可能跨不同的線程訪問變量(如果該語句錯誤,請糾正我,這正是我在某處讀到的)。

然而,現在在這個代碼示例中,它似乎不應該工作:

class MyClass
{
    public int variable;

    internal MyClass()
    {
        Thread thread = new Thread(new ThreadStart(DoSomething));
        thread.IsBackground = true;
        thread.Start();
    }

    public void DoSomething()
    {
        variable = 0;
        for (int i = 0; i < 10; i++)
            variable++;

        MessageBox.Show(variable.ToString());
    }
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void SomeMethod();
    {
        MyClass mc = new MyClass();
    }
}

當我運行SomeMethod()不應該.NET拋出異常,因為創建的對象mc運行在與mc -initializer中創建的線程不同的線程中,並且這個新線程正在嘗試訪問mc的局部變量?

MessageBox顯示10 (不)預期,但我不知道為什么這應該工作。

也許我不知道要搜索什么,但我找不到任何線程主題,會解決這個問題,但也許我對變量和線程的想法是錯誤的。

根據我對.NET的了解,不可能跨不同的線程訪問變量。 如果該陳述是錯誤的,請糾正我,這正是我在某處讀到的。

這句話完全是假的,所以請考慮一下你的修正。

您可能在某處讀過不能在不同線程上訪問局部變量的地方。 該陳述也是錯誤的,但通常是陳述。 正確的說法是那些不是的局部變量

  • 在異步方法中
  • 在迭代器塊中(即具有yield returnyield break
  • 封閉的匿名函數的外部變量

多個線程無法訪問。 甚至那個說法有點狡猾; 有方法可以用指針和unsafe代碼塊來做到這一點,但嘗試這樣做是一個非常糟糕的主意。

我還注意到你的問題詢問了局部變量,但后來給出了一個字段的例子。 根據定義,字段不是局部變量。 根據定義,局部變量是方法體的本地變量。 (或構造函數體,索引器主體等)確保您清楚。 本地的定義特征不是它“在堆棧中”或某種類似的東西; 本地的“本地”部分是它的名稱在方法體外沒有意義

在一般情況下:變量是指存儲器存儲位置 線程是進程中的控制點,進程中的所有線程共享相同的內存; 這就是使他們成為線程而不是進程的原因 因此,通常情況下,所有變量都可以由多個線程在所有時間和所有順序中訪問, 除非采用某種機制來防止這種情況發生

讓我再說一遍,只是為了確保它在你的腦海中絕對清晰:考慮單線程程序的正確方法是所有變量都是穩定的,除非有些東西使它們發生變化。 考慮多線程程序的正確方法是所有變量都在不按特定順序進行變異 ,除非某些變量保持靜止或有序。 這是多線程共享內存模型如此困難的根本原因 ,也就是為什么要避免它。

在您的特定示例中,兩個線程都可以訪問this ,因此兩個線程都可以看到變量this.variable 您沒有實現任何機制來阻止這種情況,因此兩個線程都可以按任何順序寫入和讀取該變量,但實際上受到的限制非常少。 您可以實現以馴服此行為的一些機制是:

  • 將變量標記為ThreadStatic 這樣做會導致在每個線程上創建一個新變量。
  • 將變量標記為volatile 這樣做會對可能觀察到的讀取和寫入順序施加某些限制,並且還會對編譯器或CPU可能導致意外結果的優化施加某些限制。
  • 圍繞變量的每次使用放置一個lock語句。
  • 首先不要共享變量。

除非您對多線程和處理器優化有深入的了解,否則我建議除了后者之外的任何選項。

現在,假設您確實希望確保在另一個線程上對變量的訪問失敗。 您可以讓構造函數捕獲創建線程的線程ID並將其存儲起來。 然后,您可以通過屬性getter / setter訪問該變量,其中getter和setter檢查當前線程ID,如果它與原始線程ID不同,則拋出異常。

基本上它的作用是滾動你自己的單線程公寓線程模型 “單線程單元”對象是一個只能在創建它的線程上合法訪問的對象。 (你買一台電視,你把它放在你的公寓里,只允許你公寓里的人看你的電視。)單線程公寓與多線程公寓和免費線程的細節相當復雜; 有關更多背景,請參閱此問題。

你能解釋一下STA和MTA嗎?

這就是為什么,例如,您必須永遠不能從工作線程訪問您在UI線程上創建的UI元素; UI元素是STA對象。

根據我對.NET的了解,不可能跨不同的線程訪問變量(如果該語句錯誤,請糾正我,這正是我在某處讀到的)。

這是不正確的。 可以從范圍內的任何位置訪問變量。

從多個線程訪問相同的變量時需要謹慎,因為每個線程可以在非確定性時間對變量進行操作,從而導致細微的,難以解決的錯誤。

有一個出色的網站,涵蓋從基礎到高級概念的.NET中的線程。

http://www.albahari.com/threading/

我有點晚了,@ Eric J.給出的答案非常精彩而且非常重要。

我只想在你對線程和變量的看法中為另一個問題添加一些清晰度。

你在你的問題的標題“在另一個線程中訪問變量”中說過這個。 除此之外,在您的代碼中,您正在從1個線程訪問您的變量,這是在此處創建的線程:

    Thread thread = new Thread(new ThreadStart(DoSomething));
    thread.IsBackground = true;
    thread.Start();

所有這些事情讓我意識到你害怕與實際創建MyClass實例的線程不同的線程將使用來自該實例內部的東西。

以下事實對於更清晰地了解多線程是什么非常重要(它比您想象的更簡單):

  • 線程不擁有變量,它們擁有堆棧,堆棧可能包含一些變量,但這不是我的觀點
  • 創建類的實例的線程與該線程之間沒有內在聯系。 它由所有線程擁有,就像它們不屬於任何線程一樣。
  • 當我說這些事情時,我不是在談論線程堆棧,但有人可能會說線程和實例是兩組獨立的對象,只是為了更大的好處而進行交互:)

編輯

我看到線程安全這個詞出現在這個答案的主題上。 萬一你可能想知道這些詞是什么意思我推薦這篇由@Eric Lippert撰寫的精彩文章: http//blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing -你的呼叫線程safe.aspx

不,你有它倒退,只要它仍然在范圍內,數據是可訪問的。

您需要防范相反的問題,兩個線程同時訪問相同的數據,這稱為競爭條件。 您可以使用lock等同步技術來防止這種情況發生,但如果使用不正確,可能會導致死鎖。

閱讀.NET中的C#Threading以獲得教程。

內存位置不會隔離到單個線程。 如果它們真的很不方便。 CLR中的內存僅在應用程序域邊界處隔離。 這就是每個AppDomain每個靜態變量都有一個單獨實例的原因。 但是, 線程不依賴於任何一個特定的應用程序域 它們可以在多個應用程序域中執行代碼,也可以不執行任何代碼(非托管代碼)。 他們不能做的是同時從多個應用程序域執行代碼。 這意味着線程無法同時訪問來自兩個不同應用程序域的數據結構。 這就是為什么你必須使用編組技術(例如通過MarshalByRefObject )或使用.NET Remoting或WCF等通信協議來訪問另一個應用程序域的數據結構。

考慮以下用於托管CLR的進程的unicode art圖。

┌Process───────────────────────────────┐
│                                      │
│ ┌AppDomain───┐        ┌AppDomain───┐ │
│ │            │        │            │ │ 
│ │       ┌──────Thread──────┐       │ │
│ │       │                  │       │ │
│ │       └──────────────────┘       │ │
│ │            │        │            │ │
│ └────────────┘        └────────────┘ │
└──────────────────────────────────────┘

您可以看到每個進程可以有多個應用程序域,並且一個線程可以從多個進程中執行代碼。 我還嘗試說明一個線程也可以通過在左右AppDomain塊之外顯示它的存在來執行非托管代碼這一事實。

所以基本上一個線程對當前正在執行的同一個應用程序域中的任何數據結構都進行了簡單而重要的訪問。我在這里使用術語“瑣碎”來包括內存訪問(數據結構,變量等)從一個班級到另一個班級的公共,受保護或內部成員。 線程決不會阻止這種情況發生。 但是,使用反射,您甚至可以訪問另一個類的私有成員。 這就是我所說的非平凡訪問。 是的,它涉及到你自己的一些工作,但是一旦你完成了反射調用就沒有任何花哨的東西了(順便提一下,代碼訪問安全性必須允許這樣做,但這是一個不同的主題)。 關鍵是一個線程可以訪問它正在執行的同一個應用程序域中的幾乎所有內存。

線程可以訪問同一應用程序域中的幾乎所有內容的原因是因為如果它沒有,那將是非常嚴格的限制。 在多線程環境中工作時,開發人員必須付出額外的努力來在類之間共享數據結構。

所以總結一下要點:

  • 數據結構(類/結構)或其組成成員與線程之間沒有一對一的關系。
  • 線程和應用程序域之間沒有一對一的關系。
  • 從技術上講,OS線程和CLR線程之間甚至沒有一對一的關系(盡管實際上我知道沒有偏離該方法的CLI的主流實現1 )。
  • 顯然,CLR線程仍然局限於創建它的進程。

1 即使是Singularity操作系統似乎也直接將.NET線程映射到操作系統和硬件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM