C#多线程Task常见问题(二)

1 多线程临时变量

2 线程安全和锁lock

3 线程安全策略总结


线程安全和锁lock

线程安全问题:一段程序逻辑在单线程中执行和多线程中执行,结果一致说明线程是安全的;如果结果不同说明线程不安全。

同样先看一个例子:分别用主线程和Task线程池循环10次,并对List数组进行添加,最后打印数组的个数。

            List intList1 = new List();
            //主线程循环10次
            for (int i = 0; i < 10; i++)
            {
                intList1.Add(i);
            }
            Console.WriteLine($"intList1的个数:{intList1.Count.ToString()}");
            //线程池循环10次
            List intList2 = new List();
            List taskList = new List();
            for (int i = 0; i < 5; i++)
            {
               
                taskList.Add(Task.Run(() =>
                 {
                     intList2.Add(i);
                 }));
            }
            Task.WaitAll(taskList.ToArray());
            Console.WriteLine($"intList2的个数:{intList2.Count.ToString()}");
            Console.ReadKey();

打印效果:

C#多线程Task常见问题(二)_第1张图片

可以看到主线程运行的List数组个数是10个,多线程是5个。为什么出现这种情况?

 主要原因是:在多线程执行过程中,由于是同时执行多个循环,会存在几个循环同时对一个List数组添加的情况,造成内存资源抢占。

如何解决??????

(1) 通过加锁的方式:普通锁 Object 

该方式的本质是独占引用,即将该块程序变成单线程执行,虽然可以解决多线程问题,但是影响执行性能,不推荐使用。

  private static object obj_lock =new object();//必须是静态类型

=>实例:

        private static object obj_lock = new object();
        static void Main(string[] args)
        {
            
            List intList1 = new List();
            //主线程循环10次
            for (int i = 0; i < 10; i++)
            {
                intList1.Add(i);
            }
            Console.WriteLine($"intList1的个数:{intList1.Count.ToString()}");
            //线程池循环10次
            List intList2 = new List();
            List taskList = new List();
            for (int i = 0; i < 10; i++)
            {
               
                taskList.Add(Task.Run(() =>
                 {
                    lock(obj_lock)//上锁
                    {
                      intList2.Add(i);
                    }
                 }));
            }
            Task.WaitAll(taskList.ToArray());
            Console.WriteLine($"intList2的个数:{intList2.Count.ToString()}");
            Console.ReadKey();
        }

打印效果:

 (2) 将多线程任务自定义切分为数个单线程任务,保证了每个单线程内执行数据安全。

假设有10000个任务,可以将这些任务分为若干块,然后调用线程进行处理,最后执行完毕后将结果汇总。 难点:如何合理对任务进行程序切分设计

            int a = 3000;
            int b = 6000;
            int c = 10000;
            List intList1 = new List();
            List intList2 = new List();
            List intList3 = new List();
            var t1 = Task.Run(() =>
            {
                for (int i = 0; i < a; i++)
                {
                    intList1.Add(i);
                }
            });
            var t2= Task.Run(() =>
            {
                for (int i = a; i < b; i++)
                {
                    intList2.Add(i);
                }
            });
            var t3 = Task.Run(() =>
            {
                for (int i = b; i < c; i++)
                {
                    intList3.Add(i);
                }
            });
            Task.WaitAll(new Task[] { t1, t2, t3 });
            intList1.AddRange(intList2);
            intList1.AddRange(intList3);
            Console.WriteLine($"intList1的个数:{intList1.Count.ToString()}");
            Console.ReadKey();

打印效果:

C#多线程Task常见问题(二)_第2张图片

 (3) 使用线程安全对象

 常用的List/ArrayList/Queue等数据结构都不是线程安全数据结构。 将List数组换为BlockingCollection/ConcurrentQueue/ConcurrentDictionary等。需要引入:

using System.Collections.Concurrent;

实例如下:

            //线程池循环10次
            BlockingCollection intList2 = new BlockingCollection(); //采用安全线程数据结构
            List taskList = new List();
            for (int i = 0; i < 100; i++)
            {

                taskList.Add(Task.Run(() =>
                {
                   
                        intList2.Add(i);
                   
                }));
            }
            Task.WaitAll(taskList.ToArray());
            Console.WriteLine($"intList2的个数:{intList2.Count.ToString()}");
            Console.ReadKey();

            //打印效果:
            // intList2的个数:100

你可能感兴趣的:(#,C#多线程Task常见问题,开发语言,c#)