在多个线程中使用锁以及锁对象的存储副本

发布于 2025-01-12 00:08:17 字数 3620 浏览 2 评论 0原文

我正在尝试将锁与传递到工作线程的共享对象一起使用。在下面的代码中,如果我在 Worker 的 Execute 方法中传入syncLock对象,则一切正常。

但是,如果我在 Worker 类中存储syncLock 对象的本地副本,它就不起作用。

显然,当我执行“_syncLock =syncLock;”时分配,我得到一个新对象,而不是对共享syncLock对象的引用。所以我最终每个线程现在都有自己的同步锁而不是共享对象。

有没有办法存储对共享对象的本地引用?我认为对象赋值在 C# 中始终是“引用”?

Worker.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CacheConcurrency
{
    class Worker
    {
        int _ID;
        string _Request;
        object _syncLock;
        MyCache _TheCache;
        public Worker(int ID, string Request, ref object syncLock, ref MyCache TheCache)
        {
            _ID = ID;
            _Request = Request;
            _syncLock = syncLock;
            _TheCache = TheCache;
        }
        public void Execute()
        {
            lock (_syncLock)
            {
                if (_TheCache == null)
                {
                    Thread.Sleep(2000);
                    _TheCache = new MyCache();
                    _TheCache.LoadCache();
                    Console.WriteLine("thread {0}: created and loaded the cache", _ID);
                }
                else
                {
                    Console.WriteLine("thread {0}: using the existing cache", _ID);
                    Console.WriteLine("TheCache.MyCacheValue {0}", _TheCache.MyCacheValue);
                    Console.WriteLine("TheCache.CacheTimeStamp {0}", _TheCache.CacheTimeStamp);
                }
            }

            Console.WriteLine("worker DoSomething: {0}", _Request);
        }
    }
}

Main.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CacheConcurrency
{    
    class Main
    {
        public MyCache TheCache = null;
        public object syncLock = new object();

        public void Execute()
        {
            List<Task> TaskList = new List<Task>();

            for (int i = 0; i < 10; i++)
            {
                Task _Task = new Task(() => DoSomething(i, "test"));
                _Task.Start();
                Console.WriteLine("started Thread={0} at {1}", _Task.Id, DateTime.Now.ToString("hh:mm:ss.fff tt"));
            }

            Console.WriteLine("Press any key to quit the program");
            while (Console.ReadKey().KeyChar == 0) ;

        }

        void DoSomething(int ID, string Request)
        {
            Worker worker = new Worker(ID, Request, ref syncLock, ref TheCache);
            worker.Execute();
        }
    }
}

MyCache.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CacheConcurrency
{
    class MyCache
    {
        public int MyCacheValue;
        public DateTime CacheTimeStamp;
        public MyCache()
        {
            
        }

        public void LoadCache()
        {
            MyCacheValue = 1;
            CacheTimeStamp = DateTime.Now;            
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CacheConcurrency
{
    class Program
    {
        static void Main(string[] args)
        {
            Main main = new Main();
            main.Execute();
        }
    }


}

I am trying to use lock with a shared object which I pass into my worker thread. In the code below, if I pass in the syncLock object in the Execute method of Worker, everything works fine.

However, if I store a local copy of the syncLock object in my Worker class, it does not work.

Obviously when I'm doing the "_syncLock = syncLock;" assignment, instead of having a reference to the shared syncLock object, I'm getting a new object. So I end up with each thread having it's own syncLock now instead of the shared object.

Is there way to store a local reference to the shared object? I thought that an object assignment is always a "reference" in C#?

Worker.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CacheConcurrency
{
    class Worker
    {
        int _ID;
        string _Request;
        object _syncLock;
        MyCache _TheCache;
        public Worker(int ID, string Request, ref object syncLock, ref MyCache TheCache)
        {
            _ID = ID;
            _Request = Request;
            _syncLock = syncLock;
            _TheCache = TheCache;
        }
        public void Execute()
        {
            lock (_syncLock)
            {
                if (_TheCache == null)
                {
                    Thread.Sleep(2000);
                    _TheCache = new MyCache();
                    _TheCache.LoadCache();
                    Console.WriteLine("thread {0}: created and loaded the cache", _ID);
                }
                else
                {
                    Console.WriteLine("thread {0}: using the existing cache", _ID);
                    Console.WriteLine("TheCache.MyCacheValue {0}", _TheCache.MyCacheValue);
                    Console.WriteLine("TheCache.CacheTimeStamp {0}", _TheCache.CacheTimeStamp);
                }
            }

            Console.WriteLine("worker DoSomething: {0}", _Request);
        }
    }
}

Main.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CacheConcurrency
{    
    class Main
    {
        public MyCache TheCache = null;
        public object syncLock = new object();

        public void Execute()
        {
            List<Task> TaskList = new List<Task>();

            for (int i = 0; i < 10; i++)
            {
                Task _Task = new Task(() => DoSomething(i, "test"));
                _Task.Start();
                Console.WriteLine("started Thread={0} at {1}", _Task.Id, DateTime.Now.ToString("hh:mm:ss.fff tt"));
            }

            Console.WriteLine("Press any key to quit the program");
            while (Console.ReadKey().KeyChar == 0) ;

        }

        void DoSomething(int ID, string Request)
        {
            Worker worker = new Worker(ID, Request, ref syncLock, ref TheCache);
            worker.Execute();
        }
    }
}

MyCache.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CacheConcurrency
{
    class MyCache
    {
        public int MyCacheValue;
        public DateTime CacheTimeStamp;
        public MyCache()
        {
            
        }

        public void LoadCache()
        {
            MyCacheValue = 1;
            CacheTimeStamp = DateTime.Now;            
        }
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CacheConcurrency
{
    class Program
    {
        static void Main(string[] args)
        {
            Main main = new Main();
            main.Execute();
        }
    }


}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情泪▽动烟 2025-01-19 00:08:17

我解决了这个问题,感谢让我走上正轨的评论。特别感谢 MickyD,他对需要线程安全的代码进行了评论,这让我找到了答案。

事实证明,我在 Execute 函数中正确地使用了锁进行了保护,但是在我的构造函数中,我仍然在没有锁的情况下引用共享 Cache 对象,这不是线程安全的,并且会导致竞争条件。

解决方法是将构造函数调用和执行都放在关键部分/锁定块内,如下所示:

void DoSomething(int ID, string Request)
{
    lock(syncLock) 
    {
        Worker worker = new Worker();
        worker.Execute(ID, Request, ref TheCache);
    }
}

这也简化了工作代码,避免了存储syncLock对象的本地引用并在其中执行锁定的需要。

我也同意 MickyD 的其他评论,即应该重新编写此代码以使用 async/await,而不是在工作线程内使用缓存等。我们使用此代码库的寿命是有问题的,因此投资技术债务是不确定的眼下。

I worked out the problem, thanks for the comments who got me on the right track. In particular, thanks to MickyD who commented on the code needing to be thread-safe which led me to the answer.

It turns out that I was protecting with lock correctly in my Execute function, however in my constructor I was still referencing the shared Cache object without a lock, which was not thread-safe and causing a race condition.

The fix is to put both the constructor call and the Execute inside the critical section/lock block like so:

void DoSomething(int ID, string Request)
{
    lock(syncLock) 
    {
        Worker worker = new Worker();
        worker.Execute(ID, Request, ref TheCache);
    }
}

This also simplifies the worker code, avoiding the need to store a local reference of the syncLock object and doing a lock in there.

I also agree with MickyD's other comments that this code should be re-written to use async/await, not have a cache inside the worker etc. The longevity of us using this code base is in question, so investing in the tech debt is uncertain at the moment.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文