访问变量时的并发性和线程安全性
本文关键字:线程 安全性 并发 变量 访问 | 更新日期: 2023-09-27 18:08:18
在编写多线程应用程序时,应该如何积极地使用锁定类变量?例如,我有一个类字段,只能由一个线程更改(每秒运行),并由多个线程访问,这些线程将引用复制到局部变量,然后使用它,并且可以运行频率为1毫秒。
我想知道我是否可以像这样简单地使用引用数据字段:
public class OneThread {
public static DataHolder Data = new DataHolder();
public void CalledEverySecond() {
Data = new DataHolder();
}
}
public class MultiThread {
public void RunsReallyOftenCalledFromMultipleThreads() {
Data local = OneThread.Data;
// now work with local reference
// I suppose that it won't change and this is legal usage
// i.e. I don't need to pollute this code with bunch of lock
// or Monitor.Enter statements
}
}
当然,我可以开始抛出lock和Monitor语句,但我想知道在这样的场景中我是否真的需要它们。请回答只有当你能够提供代码,并有相当多的多线程编码经验-我不打算开始另一个线程,将讨论最佳实践和理论。
使用这样的本地引用似乎没有问题。我写了以下类:
public class DataHolder
{
public int Id { get; set; }
public string Name { get; set; }
public static DataHolder InitRandom()
{
var random = new Random();
return new DataHolder() {Id = random.Next(), Name = random.Next().ToString()};
}
}
public class OneThread
{
public static readonly OneThread Instance = new OneThread();
public OneThread()
{
Data = DataHolder.InitRandom();
}
public DataHolder Data;
// this method is called often by one thread
public void RunsEverySecond()
{
Data = DataHolder.InitRandom();
}
}
public class MultiThread
{
public static List<string> Results = new List<string>();
// called externaly by multiple threads
public static bool SomeMethod()
{
var loc = OneThread.Instance.Data;
// initializing new Random every time on purpose since that also takes time
Thread.Sleep(new Random().Next(0,3));
var id = loc.Id;
var name = loc.Name;
var res = loc.Id == id && loc.Name == name;
if (!res)
throw new Exception("different");
var log = String.Format("{0} - OneThreadId: {1}, loc.Id: {2}, id: {3}", res, OneThread.Instance.Data.Id,
loc.Id, id);
lock (Results)
{
Results.Add(log);
}
return res;
}
public static void SpitResults()
{
File.WriteAllLines("C:''res.txt", Results);
}
}
为了进行测试,我创建了一个带有两个按钮的Window Form应用程序,用于初始化类并调用它们:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
_secondTimer = new Timer(state => OneThread.Instance.RunsEverySecond(), null, 0, 2);
}
private Timer _secondTimer;
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10000; i++)
{
ThreadPool.QueueUserWorkItem(o => MultiThread.SomeMethod());
}
}
private void button2_Click(object sender, EventArgs e)
{
MultiThread.SpitResults();
}
}
多次运行此测试后,我得到以下结果:
True - OneThreadId: 968900563, loc.Id: 968900563, id: 968900563
True - OneThreadId: 968900563, loc.Id: 968900563, id: 968900563
True - OneThreadId: 1739428491, loc.Id: 968900563, id: 968900563
True - OneThreadId: 1739428491, loc.Id: 968900563, id: 968900563
True - OneThreadId: 1739428491, loc.Id: 968900563, id: 968900563
True - OneThreadId: 1739428491, loc.Id: 968900563, id: 968900563
True - OneThreadId: 1739428491, loc.Id: 968900563, id: 968900563
True - OneThreadId: 1739428491, loc.Id: 1739428491, id: 1739428491
True - OneThreadId: 1739428491, loc.Id: 1739428491, id: 1739428491
True - OneThreadId: 1739428491, loc.Id: 968900563, id: 968900563
所以,我的结论是侵略性锁在这种情况下根本不需要,如果我"复制"类字段到局部变量,就不会有任何问题——那里的值不会改变,可以放心使用。
如果你发现我的发现有任何错误,请发表评论/随时编辑。