【问题标题】:Multiple threads access a return method is it thread safe with a lock?多个线程访问一个返回方法是带锁的线程安全吗?
【发布时间】:2019-06-26 19:30:15
【问题描述】:

我正在尝试从多个线程访问一个方法,我不确定这是否是线程安全的。如果不是,那么确保其安全的最有效方法是什么?

我考虑了每个人的见解,我的最终产品如下所示,我还选择了对数据包本身调用回收。

我的数据包池类

using System.Collections.Generic;

namespace Networking
{
    public class PacketPool
    {
        private Queue<Packet> pool = new Queue<Packet>();
        private readonly object instance = new object();

        public Packet CreatePacket(string method)
        {
            lock (instance)
                return pool.Count == 0 ? new Packet() { Pool = this } : 
                       pool.Dequeue();
        }

        public void Recycle(Packet packet)
        {
            lock(instance)
               pool.Enqueue(packet);
        }
    }
}

数据包类

using System;
using System.Net;

namespace Networking
{
    public class Packet 
    {
        public Protocol Proto = Protocol.Sequenced;
        public PacketFlags Flag = PacketFlags.None;
        public Fragment Fragmented = Fragment.NotFragged;
        public SendType SendType = SendType.Raw;
        public EndPoint RemoteEp = new IPEndPoint(IPAddress.Any, 0);
        public byte[] Buffer = new byte[512];
        public int Count = 0;
        public int Ptr = 8;
        public int Bits { get; set; } = 0;
        public ushort Id = 0;
        public ushort Lead = 0;
        public PacketPool Pool;

        public void Recycle()
        {
            Bits = 0;

            if (Buffer.Length > 512)
                Array.Resize(ref Buffer, 512);

            Count = 0;
            Flag = PacketFlags.None;
            Fragmented = Fragment.NotFragged;
            Proto = Protocol.Sequenced;
            Ptr = 8;
            Lead = 0;
            SendType = SendType.Raw;
            Pool.Recycle(this);
        }
    }
}

希望上述解决方案能让生活更轻松。

【问题讨论】:

  • 答案中指出了这一点,但为了进一步强调,public Queue&lt;Packet&gt; pool = new Queue&lt;Packet&gt;(2000); 违背了lock 的目的。任何引用PacketPool 的东西都可以在不获取该锁的情况下使项目入队或出队,因为Queue 已暴露。这并不好,即使除了线程问题。如果您将其设为private,则确保只有类中的方法才能使项目入队或出队。
  • 我现在将进行这些更改 :)
  • 你为什么不使用ConcurrentQueue&lt;T&gt;,这基本上会让你问题中的大部分代码立即消失...... ;-)
  • 好吧,我不知道残余影响是什么,但我猜它们与 ConcurrentQueue 本身没有直接关系。 (啊,刚刚注意到我之前的评论中有一个错误。我的意思是说“大部分锁定代码”,而不是“大部分代码”。这会很有趣如果 ConcurrentQueue 会神奇地替换那里的数据包实例重置......)
  • 第二个lock语句里面有很多行代码。他们都需要保护吗?您应该在尽可能短的时间内持有锁,以减少争用。

标签: c# multithreading thread-safety


【解决方案1】:

您展示的代码看起来是线程安全的,只要数据包本身没有在单独的线程上进行修改。您也可以考虑让 PacketPool 类线程安全,以节省一些宝贵的锁定时间。

您可以通过将Queue 替换为ConcurrentQueue 来简化它。

请注意,您正在修改发送到Recycle 方法的数据包。我建议使数据对象不可变,以避免意外行为。

public class PacketPool
{
    public ConcurrentQueue<Packet> pool = new ConcurrentQueue<Packet>(2000);

    public Packet CreatePacket(string method)
    {
        if (pool.TryDequeue(out Packet packet))
        {
            return packet;
        }

        return new Packet();
    }

    public void Recycle(Packet packet)
    {
        packet.Bits = 0;

        if (packet.Buffer.Length > 512)
            Array.Resize(ref packet.Buffer, 512);

        packet.Count = 0;
        packet.Flag = PacketFlags.None;
        packet.Fragmented = Fragment.NotFragged;
        packet.Proto = Protocol.Sequenced;
        packet.Ptr = 8;
        packet.Lead = 0;
        packet.SendType = SendType.Raw;
        pool.Enqueue(packet);
    }
}

【讨论】:

  • 它们是在单独的线程上编辑的,例如,假设线程 1 和线程 2 请求一个数据包。我假设池将向线程 1 发送一个新数据包/或排队数据包,而线程 2 会这样做相同。然后线程 1 可以编辑返回的数据包,因为线程 2 有自己的要编辑。希望这是有道理的?
  • 是的,这是有道理的。只要他们在调用Recycle 后不使用数据包,听起来就可以。 (因为另一个线程可能会使同一个数据包出队。如果该数据包在回收后被单独的线程修改,那将是一种耻辱。)因此,从长远来看,这种共享可能会导致一些麻烦。
  • ^^ 您说的正是令人头疼的问题。这正是正在发生的事情,我认为这是允许修改其他数据包的锁。如果我要在 1kb 到 100mb 之间的任何地方每秒创建 17k 个数据包,那么我有数据包回收以减少 GC,那么 GC 会失控。总而言之,感谢您的帮助,我真的很感激。我可能会尝试减少线程并找出数据包被修改的位置。
  • 也许您可以在“数据包”类上创建一个“已回收”属性。然后在回收时将回收设置为true,在创建时设置为“假”。如果回收时使用Packet 的任何属性为true,则抛出PacketRecycledException。这可能会显示您的数据包不应该被使用的地方。
  • 繁荣,这是有史以来最伟大的想法。谢谢你发现问题?。
【解决方案2】:

如果数据包不是跨线程共享的,那么由于锁,这是线程安全的。

小心公共场池。您也可以在不安全的线程上编辑它。

请注意,建议使用专用的锁对象,否则可能会出现死锁 - 例如,另一个进程锁定了公共属性 Pool。建议将锁对象设为私有并仅在该类中使用它(所以上一级)

同步线程访问共享资源时,锁定一个专用对象实例(例如,私有只读对象 balanceLock = new object();)或另一个不太可能被无关部分用作锁定对象的实例的代码。避免对不同的共享资源使用相同的锁对象实例,因为这可能会导致死锁或锁争用。

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement

【讨论】:

  • 创建数据包的线程只会使用数据包。所以我不认为它是共享的。我将修复上面的代码以使用专用对象。谢谢-Levon
  • 所做的更改我相信这是正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-15
  • 1970-01-01
  • 2011-10-07
  • 2020-09-07
  • 1970-01-01
  • 2012-02-14
  • 1970-01-01
相关资源
最近更新 更多