访问变量时的并发性和线程安全性

本文关键字:线程 安全性 并发 变量 访问 | 更新日期: 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

所以,我的结论是侵略性锁在这种情况下根本不需要,如果我"复制"类字段到局部变量,就不会有任何问题——那里的值不会改变,可以放心使用。

如果你发现我的发现有任何错误,请发表评论/随时编辑。