试图理解C#中的多线程

时间:2011-07-05 21:31:25

标签: c# multithreading new-operator

我正在尝试理解多线程的基础知识,所以我构建了一个提出一些问题的小程序,我会感谢任何帮助:)

这是小程序:

class Program
{
    public static int count;
    public static int max;
    static void Main(string[] args)
    {
        int t = 0;
        DateTime Result;
        Console.WriteLine("Enter Max Number : ");
        max = int.Parse(Console.ReadLine());
        Console.WriteLine("Enter Thread Number : ");
        t = int.Parse(Console.ReadLine());

        count = 0;

        Result = DateTime.Now;
        List<Thread> MyThreads = new List<Thread>();
        for (int i = 1; i < 31; i++)
        {
            Thread Temp = new Thread(print);
            Temp.Name = i.ToString();
            MyThreads.Add(Temp);
        }

        foreach (Thread th in MyThreads)
            th.Start();

        while (count < max)
        {
        }

        Console.WriteLine("Finish , Took : " + (DateTime.Now - Result).ToString() + " With : " + t + " Threads.");
        Console.ReadLine();
    }

    public static void print()
    {
        while (count < max)
        {
            Console.WriteLine(Thread.CurrentThread.Name + " - " + count.ToString());
            count++;
        }
    }
}

我通过一些测试运行检查了这个:

我的最大数字是100,似乎最快的执行时间是2个线程,比10个线程的时间快80%。

问题:

1)线程4-10甚至不打印一次,怎么可能?

2)不应该更多的线程更快?

我将最大数量设为10000并禁用打印。

使用此配置,5个线程似乎最快。

为什么与第一次检查相比有变化?

此外,在此配置中(使用打印)所有线程都会打印几次。为什么与第一次运行只有少数线程打印不同?

是否有办法让所有线程逐个打印?在一条线或类似的东西?

非常感谢你的帮助:))

3 个答案:

答案 0 :(得分:7)

您的代码无疑是迈向线程世界的第一步,您刚刚经历了第一次(很多)头痛!

首先,static可以让您在线程中共享变量,但它不会以线程安全方式执行此操作。这意味着您的count < max表达式和count++不能保证最新或线程之间的有效保护。当max仅为10(t设置为4,在我的8处理器工作站上)时,查看程序的输出:

T0 - 0
T0 - 1
T0 - 2
T0 - 3
T1 - 0 // wait T1 got count = 0 too!
T2 - 1 // and T2 got count = 1 too!
T2 - 6
T2 - 7
T2 - 8
T2 - 9
T0 - 4
T3 - 1 // and T3 got count = 1 too!
T1 - 5

关于每个线程打印逐个的问题,我假设您正在尝试协调对count的访问。您可以使用同步原语(例如C#中的lock语句)来完成此操作。以下是对代码的简单修改,以确保仅发生max增量:

static object countLock = new object();

public static void printWithLock()
{
    // loop forever
    while(true)
    {
        // protect access to count using a static object
        // now only 1 thread can use 'count' at a time
        lock (countLock)
        {
            if (count >= max) return;

            Console.WriteLine(Thread.CurrentThread.Name + " - " + count.ToString());
            count++;
        }
    }
}

这个简单的修改使您的程序在逻辑上正确,但。该示例现在出现了一个新问题:锁争用。现在每个线程都在争夺对countLock的访问权限。我们已经使程序线程安全,但没有并行性的任何好处!

线程和并行性并不是特别容易理解,但幸运的是.Net的最新版本附带Task Parallel Library (TPL)Parallel LINQ (PLINQ)

图书馆的美妙之处在于转换当前代码是多么容易:

var sw = new Stopwatch();

sw.Start();
Enumerable.Range(0, max)
          .AsParallel()
          .ForAll(number =>
               Console.WriteLine("T{0}: {1}",
                                 Thread.CurrentThread.ManagedThreadId,
                                 number));

Console.WriteLine("{0} ms elapsed", sw.ElapsedMilliseconds);

// Sample output from max = 10
// 
// T9: 3
// T9: 4
// T9: 5
// T9: 6
// T9: 7
// T9: 8
// T9: 9
// T8: 1
// T7: 2
// T1: 0
// 30 ms elapsed

上面的输出是一个有趣的例子,说明为什么线程会为新用户产生“意外结果”。当线程并行执行时,它们可以在不同的时间点完成代码块,或者一个线程可能比另一个线程更快。你永远不会真正了解线程!

答案 1 :(得分:0)

您的打印功能远非线程安全,这就是4-10无法打印的原因。所有线程共享相同的max和count变量。

为什么更多线程减慢速度的原因可能是每次处理器在每个线程之间改变焦点时发生状态更改。

此外,当您创建大量线程时,系统需要分配新线程。大多数情况下,现在建议使用Tasks,因为它们是从系统管理的线程池中提取的。因此不一定必须分配。创建一个独特的新线程相当昂贵。

无论如何看看这里:http://msdn.microsoft.com/en-us/library/aa645740(VS.71).aspx

答案 2 :(得分:0)

仔细看:

    t = int.Parse(Console.ReadLine());

    count = 0;

    Result = DateTime.Now;
    List<Thread> MyThreads = new List<Thread>();
    for (int i = 1; i < 31; i++)
    {
        Thread Temp = new Thread(print);
        Temp.Name = i.ToString();
        MyThreads.Add(Temp);
    }

我认为你错过了变量 t (i <31)。

在编写代码之前,您应该阅读许多关于并行和多线程编程的书籍,因为编程语言只是一种工具。祝你好运!