2017-08-15 86 views
0

这是关于Apress出版的并行编程的书线程本地存储(TLS)的例子。我知道如果我们有4个核心的计算机,4个线程可以同时并行运行。在这个例子中,我们创建了10个任务,我们假设有4个核心的计算机。每个线程本地存储都驻留在线程上,所以当启动10个任务并行时只有4个线程执行。我们有4个TLS,所以10个任务尝试更改4个线程本地存储对象。我想问Tls如何防止数据竞争问题线程数<任务计数?线程本地存储工作原理

using System; 
using System.Threading; 
using System.Threading.Tasks; 
namespace Listing_04 
{ 
    class BankAccount 
    { 
     public int Balance 
     { 
      get; 
      set; 
     } 
    } 
    class Listing_04 
    { 
     static void Main(string[] args) 
     { 
      // create the bank account instance 
      BankAccount account = new BankAccount(); 
      // create an array of tasks 
      Task<int>[] tasks = new Task<int>[10]; 
      // create the thread local storage 
      ThreadLocal<int> tls = new ThreadLocal<int>(); 
      for (int i = 0; i < 10; i++) 
      { 
       // create a new task 
       tasks[i] = new Task<int>((stateObject) => 
       { 
        // get the state object and use it 
        // to set the TLS data 
        tls.Value = (int)stateObject; 
        // enter a loop for 1000 balance updates 
        for (int j = 0; j < 1000; j++) 
        { 
         // update the TLS balance 
         tls.Value++; 
        } 
        // return the updated balance 
        return tls.Value; 
       }, account.Balance); 
       // start the new task 
       tasks[i].Start(); 
      } 
      // get the result from each task and add it to 
      // the balance 
      for (int i = 0; i < 10; i++) 
      { 
       account.Balance += tasks[i].Result; 
      } 
      // write out the counter value 
      Console.WriteLine("Expected value {0}, Balance: {1}", 
      10000, account.Balance); 
      // wait for input before exiting 
      Console.WriteLine("Press enter to finish"); 
      Console.ReadLine(); 
     } 
    } 
} 
+3

有任务,线程和内核的数量之间没有直接的关联......我建议你忘记CPU内核完全然后分别读取任务和线程,B)线程和TLS之间的)关系..在这一点上,你可能想要[编辑]这篇文章来澄清你不明白的东西。 –

+0

在这个例子中,如果我们有一些线程少于任务一些线程工作一个更多的任务。所以TLS在线程上工作,当一个线程正在工作多一个任务线程时TLS被不同的任务改变。我认为这是一个问题 – altandogan

+0

虽然我们在4核心机器上有很多线程,但我们可以在同一时间只运行4个线程是真的吗? – altandogan

回答

3

我们有4个TLS所以10个任务试图改变4线程本地存储对象

在你的榜样,你可以有之间的任何1个10 TLS插槽。这是因为:a)你没有明确管理你的线程,因此这些任务是使用线程池来执行的,b)线程池根据需求随时间创建和销毁线程。

只有1000次迭代的循环将彻底几乎瞬间。因此,在线程池决定工作项已经等待足够长的时间以证明添加任何新线程之前,可能所有十个任务都将通过线程池。但是这里没有保证

the documentation一些重要的部分包括以下语句:

默认情况下,线程的最小数量设置为处理器的数量在系统上

当需求低时,线程池线程的实际数量可能会低于最小值。

换句话说,你的四核系统上,线程的默认最小数量为四,但活跃在线程池中的线程实际数实际上可能少一些。如果这些任务花费足够长的时间执行,那么活动线程的数量可能会超过这个数量。

要记住这里最重要的事情是,在线程池的环境中使用TLS几乎可以肯定是错误的做法。

您可以使用TLS当你有过的线程控制,你想一个线程能够保持一些数据私人或独有的线程。这就是当您使用线程池发生的事情相反。即使在最简单的情况下,多个任务也可以使用相同的线程,因此最终会共享TLS。而在更复杂的场景,例如使用await时一样,单个任务可以结束在不同的线程执行,使得一个任务可以结束使用取决于什么的线程分配给在那一刻任务不同的TLS值。

如何防止数据竞争问题时的线程数<任务计数?

这取决于你在说什么“数据竞争问题”

事实是,您发布的代码中充满了至少是奇怪的问题,即使不是完全错误的问题。例如,您将通过account.Balance作为每个任务的初始值。但为什么?这个值是在创建任务之后进行评估的,之后它可能会被修改,那么通过它有什么意义呢?

如果您认为在任务开始时您正在传递任何当前值,那么看起来这也是错误的。为什么要让一个给定任务的起始值根据已完成的任务数量和后续循环中的计算量而变化是有效的? (需要明确的是:这是发生了什么&hellip;但即使是这样,它会是做一个奇怪的东西。)

超越一切,目前还不清楚就想到了使用TLS这里反正就完成的任务。当每个任务启动时,您将TLS值重新初始化为0(即,您传递给Task<int>构造函数的值为account.Balance)。因此,在执行任何给定任务的上下文中,除了0之外,没有任何线程可以看到其他值。一个局部变量可以完成完全相同的事情,不需要TLS的开销,也不会混淆读取代码的任何人,并试图找出在为代码添加任何值时使用TLS的原因。

那么,TLS解决某种“数据竞争问题”?不是在这个例子中,它似乎没有。所以要求如何它这样做是不可能回答的。它不这样做,所以没有“如何”。


对于它的价值,我稍微修改了您的示例,以便报告分配给任务的单个线程。我发现在我的机器上,使用的线程数在2到8之间变化。这与我的八核机器一致,由于池中的第一个线程可以在池初始化其他线程并为其分配任务之前完成了多少操作。最常见的情况是,我会看到第一个线程完成三到五个任务,其余任务由剩余的单独线程处理。

在任何情况下,线程池在任务启动后立即创建了八个线程。但是大多数情况下,至少有一个线程未被使用,因为其他线程能够在池饱和之前完成任务。也就是说,线程池只是管理任务的开销,在你的例子中,任务非常便宜,以至于这个开销允许一个或多个线程池线程完成一个任务,之后线程池需要该线程来处理另一个任务。

我已在下面复制该版本。请注意,我还在试验迭代之间添加了一个延迟,以允许线程池终止其创建的线程(在我的计算机上,这花费了20秒,因此延迟时间硬编码&hellip;您可以看到线程在调试器中终止输出)。

static void Main(string[] args) 
{ 
    while (_PromptContinue()) 
    { 
     // create the bank account instance 
     BankAccount account = new BankAccount(); 
     // create an array of tasks 
     Task<int>[] tasks = new Task<int>[10]; 
     // create the thread local storage 
     ThreadLocal<int> tlsBalance = new ThreadLocal<int>(); 
     ThreadLocal<(int Id, int Count)> tlsIds = new ThreadLocal<(int, int)>(
      () => (Thread.CurrentThread.ManagedThreadId, 0), true); 
     for (int i = 0; i < 10; i++) 
     { 
      int k = i; 
      // create a new task 
      tasks[i] = new Task<int>((stateObject) => 
      { 
       // get the state object and use it 
       // to set the TLS data 
       tlsBalance.Value = (int)stateObject; 
       (int id, int count) = tlsIds.Value; 
       tlsIds.Value = (id, count + 1); 
       Console.WriteLine($"task {k}: thread {id}, initial value {tlsBalance.Value}"); 
       // enter a loop for 1000 balance updates 
       for (int j = 0; j < 1000; j++) 
       { 
        // update the TLS balance 
        tlsBalance.Value++; 
       } 
       // return the updated balance 
       return tlsBalance.Value; 
      }, account.Balance); 
      // start the new task 
      tasks[i].Start(); 
     } 

     // Make sure this thread isn't busy at all while the thread pool threads are working 
     Task.WaitAll(tasks); 

     // get the result from each task and add it to 
     // the balance 
     for (int i = 0; i < 10; i++) 
     { 
      account.Balance += tasks[i].Result; 
     } 

     // write out the counter value 
     Console.WriteLine("Expected value {0}, Balance: {1}", 10000, account.Balance); 
     Console.WriteLine("{0} thread ids used: {1}", 
      tlsIds.Values.Count, 
      string.Join(", ", tlsIds.Values.Select(t => $"{t.Id} ({t.Count})"))); 
     System.Diagnostics.Debug.WriteLine("done!"); 
     _Countdown(TimeSpan.FromSeconds(20)); 
    } 
} 

private static void _Countdown(TimeSpan delay) 
{ 
    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); 

    TimeSpan remaining = delay - sw.Elapsed, 
     sleepMax = TimeSpan.FromMilliseconds(250); 
    int cchMax = $"{delay.TotalSeconds,2:0}".Length; 
    string format = $"\r{{0,{cchMax}:0}}", previousText = null; 

    while (remaining > TimeSpan.Zero) 
    { 
     string nextText = string.Format(format, remaining.TotalSeconds); 

     if (previousText != nextText) 
     { 
      Console.Write(format, remaining.TotalSeconds); 
      previousText = nextText; 
     } 
     Thread.Sleep(remaining > sleepMax ? sleepMax : remaining); 
     remaining = delay - sw.Elapsed; 
    } 

    Console.Write(new string(' ', cchMax)); 
    Console.Write('\r'); 
} 

private static bool _PromptContinue() 
{ 
    Console.Write("Press Esc to exit, any other key to proceed: "); 
    try 
    { 
     return Console.ReadKey(true).Key != ConsoleKey.Escape; 
    } 
    finally 
    { 
     Console.WriteLine(); 
    } 
}