【问题标题】:Locked console.writeline call works non-correctly锁定的 console.writeline 调用工作不正确
【发布时间】:2012-05-16 23:53:13
【问题描述】:

尝试从锁定部分调用 Console.WriteLine,但似乎无法正常工作 - 控制台未锁定。下面是简单应用程序的代码 - 两个线程并行填充一个列表,出于调试目的,我正在打印有关线程工作和新添加元素的信息。

using System;
using System.Threading;
using System.Collections.Generic;

class ThreadSafe
{
    static object SyncRoot = new object();

    static int threadsCount = 0;
    static List<string> list = new List<string>();

    static void Main()
    {
        Thread t1 = new Thread(AddItems);
        Thread t2 = new Thread(AddItems);
        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        PrintItems();

        Console.ReadLine();
    }

    static void PrintItems()
    {
        string[] items;

        Console.WriteLine("Converting list to array...");
        items = list.ToArray();

        Console.WriteLine("Printing array...");
        foreach (string s in items)
            Console.WriteLine(s);
    }

    static void AddItems()
    {
        int threadNo = threadsCount;
        threadsCount++;


        {
            Console.WriteLine("Populating list from {0} item in thread N {1}...", list.Count, threadNo);
            for (int i = 0; i < 50; i++)
            {
                lock (SyncRoot)
                {
                    Console.Write("Population. Thread N {0} is running. ", threadNo);
                    Console.WriteLine("Element N {0} has been added successfully.", list.Count);
                    list.Add("Item " + list.Count);
                }
            }
        }
    }
}

结果:

Populating list from 0 item in thread N 1...
Population. Thread N 1 is running. Element N 0 has been added successfully.
Population. Thread N 1 is running. Element N 1 has been added successfully.
Population. Thread N 1 is running. Element N 2 has been added successfully.
Population. Thread N 1 is running. Element N 3 has been added successfully.
Population. Thread N 1 is running. Element N 4 has been added successfully.
Population. Thread N 1 is running. Element N 5 has been added successfully.
Population. Thread N 1 is running. Element N 6 has been added successfully.
Population. Thread N 1 is running. Element N 7 has been added successfully.
Population. Thread N 1 is running. Element N 8 has been added successfully.
Population. Thread N 1 is running. Element N 9 has been added successfully.
Population. Thread N 1 is running. Element N 10 has been added successfully.
Population. Thread N 1 is running. Element N 11 has been added successfully.
Population. Thread N 1 is running. Element N 12 has been added successfully.
Population. Thread N 1 is running. Element N 13 has been added successfully.
Population. Thread N 1 is running. Element N 14 has been added successfully.
Population. Thread N 1 is running. Element N 15 has been added successfully.
Population. Thread N 1 is running. Populating list from 0 item in thread N 0...
Element N 16 has been added successfully.
Population. Thread N 0 is running. Element N 17 has been added successfully.
Population. Thread N 0 is running. Element N 18 has been added successfully.
Population. Thread N 0 is running. Element N 19 has been added successfully.

在第 15 步和第 16 步之间,新的第一次开始运行并在锁定部分中的 Console.Write 和 Console.WriteLine 调用之间输出其内容... Console.WriteLine 确实是非线程安全的吗?还是我做错了什么?

谢谢!

【问题讨论】:

  • thread N 0 的输出不在lock() 块中。没有理由不能执行此操作
  • 用锁保护共享资源的麻烦在于您必须锁定所有的访问。跳过一个,它不再正常工作。
  • @Hans Passant - 谢谢,这很有帮助!

标签: c# multithreading io thread-safety console.writeline


【解决方案1】:

您有 2 个AddItems 线程,每个线程都进行解锁输出(函数中的第一个)。这种交错是预期的行为。你需要:

  lock (SyncRoot)
  {
    Console.WriteLine("Populating list from {0} item in thread N {1}...", list.Count, threadNo); 
  }

【讨论】:

  • @Default - 不一定,因为每个循环上的锁定和解锁可能更高效或需要什么。单独的细粒度锁而不是一个粗粒度锁是原始查询范围之外的设计选择。
  • @Default - np.通常,我会完全避免跨 I/O 锁定,如果需要(就像这里一样),避免跨多个 I/O 保持,因为这可能会使竞争线程饿死。
  • @Steve Townsend - 是的,他们都进行解锁输出。我对“lock”关键字的理解是,当我将一段代码放入“lock”边界时,没有其他进程/线程可以中断“locked”代码的执行。它是否正确?如果是这样,我想知道为什么其他线程(线程 N0)的输出出现在线程 N1 的两个锁定输出之间。
  • 你的理解是不正确的(不过别担心,一开始是个常见的错误)。 lock 实际上所做的是提供独占访问相对于使用相同锁的所有线程。任何不使用锁或锁定其他东西的线程都可以继续并行运行。正如@Hans 所指出的,您不能只锁定控制台的某些使用,如果多个线程同时运行,则必须将其全部锁定。
  • + 锁定每次迭代在现实生活中可能没有任何意义,但我只是在玩多线程和每次迭代锁定保证在列表人口中的线程之间切换。在整个周期锁定的情况下,我只会让顺序线程运行,而不是列表人口的“并行性”。
【解决方案2】:

行:

Console.WriteLine("Populating list from {0} item in thread N {1}...", list.Count, threadNo);

出现在同步块之外。

【讨论】:

    【解决方案3】:

    Console.WriteLine("从线程 N {1} 中的 {0} 项填充列表...", list.Count, threadNo);不在锁定区域内,因此可以随时打印,如您所见。

    【讨论】:

      【解决方案4】:

      您的某些代码位于锁定部分之外。第一件事:

      int threadNo = threadsCount;
              threadsCount++;
      

      不是线程安全的。想象一个线程在增加线程计数之前启动并被第二个线程中断。两个线程都会有threadNo = 0

      但这似乎不是问题,尽管如果您使用它进行一些计算可能会导致问题。

      正如其他人指出的那样:您还需要锁定Console.WriteLine("Populating list from {0} item in thread N {1}...", list.Count, threadNo);,因此当下一个线程启动时,它会等待在控制台上写入,而另一个线程也在写入。

      【讨论】:

      • 是的,同意threadNo和threadsCount++的部分应该放入locked部分。我想正因为如此,我在原始代码中得到了“从 0 项填充列表”而不是“16 项”。按照您的建议完成threadsCount ++的锁定+移动了引用的Console.WriteLine +在t1.Start()和t2.Start()调用之后添加了睡眠+在列表填充周期(对于i)中添加了短睡眠(50毫秒)-结果更加明显和说明性。
      • 我认为你不需要锁定睡眠。它应该在没有睡眠的情况下工作
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 2016-08-17
      • 2015-06-06
      • 2011-07-31
      • 2011-10-17
      • 2018-04-04
      • 1970-01-01
      相关资源
      最近更新 更多