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

时间:2014-06-12 20:36:51

标签: c# multithreading

编写多线程应用程序时,锁定类变量应该是多么激进?例如,我有一个只被一个线程更改的类字段(每秒运行一次),并且由多个线程访问,这些线程复制对局部变量的引用然后使用它并且可以运行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语句,但我想知道在这样的场景中我是否真的需要它们。请仅在您能够提供代码并且具有多线程编码经验时才能回答 - 我不打算开始另一个讨论最佳实践和理论的线程。

1 个答案:

答案 0 :(得分:0)

使用像这样的本地引用似乎没有问题。我写过以下课程:

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

所以,我的结论是,在这样的情况下根本不需要积极锁定,如果我&#34;复制&#34;类字段到局部变量不会出现任何问题 - 这些值无法改变,可以毫无顾虑地使用。

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