1 多线程临时变量
2 线程安全和锁lock
3 线程安全策略总结
线程安全和锁lock
线程安全问题:一段程序逻辑在单线程中执行和多线程中执行,结果一致说明线程是安全的;如果结果不同说明线程不安全。
同样先看一个例子:分别用主线程和Task线程池循环10次,并对List数组进行添加,最后打印数组的个数。
List<int> intList1 = new List<int>();
//主线程循环10次
for (int i = 0; i < 10; i++)
{
intList1.Add(i);
}
Console.WriteLine($"intList1的个数:{intList1.Count.ToString()}");
//线程池循环10次
List<int> intList2 = new List<int>();
List<Task> taskList = new List<Task>();
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();
打印效果:
可以看到主线程运行的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<int> intList1 = new List<int>();
//主线程循环10次
for (int i = 0; i < 10; i++)
{
intList1.Add(i);
}
Console.WriteLine($"intList1的个数:{intList1.Count.ToString()}");
//线程池循环10次
List<int> intList2 = new List<int>();
List<Task> taskList = new List<Task>();
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<int> intList1 = new List<int>();
List<int> intList2 = new List<int>();
List<int> intList3 = new List<int>();
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();
打印效果:
(3) 使用线程安全对象
常用的List/ArrayList/Queue等数据结构都不是线程安全数据结构。 将List数组换为BlockingCollection/ConcurrentQueue/ConcurrentDictionary等。需要引入:
using System.Collections.Concurrent;
实例如下:
//线程池循环10次
BlockingCollection<int> intList2 = new BlockingCollection<int>(); //采用安全线程数据结构
List<Task> taskList = new List<Task>();
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