代码之家  ›  专栏  ›  技术社区  ›  Waleed Eissa

如何锁定C#中的整数?

  •  19
  • Waleed Eissa  · 技术社区  · 15 年前

    有没有办法锁定C#中的整数?整数不能与lock一起使用,因为它们是装箱的(并且只锁定引用上的锁)。

    场景如下:我有一个基于论坛的网站,有一个调节功能。我想做的是确保在任何给定的时间都不能有一个以上的版主来主持一篇文章。为了实现这一点,我想锁定post的ID。

    有什么建议吗?

    14 回复  |  直到 11 年前
        1
  •  30
  •   configurator    15 年前

    我喜欢这样做

    public class Synchronizer {
        private Dictionary<int, object> locks;
        private object myLock;
    
        public Synchronizer() {
            locks = new Dictionary<int, object>();
            myLock = new object();
        }
    
        public object this[int index] {
            get {
                lock (myLock) {
                    object result;
                    if (locks.TryGetValue(index, out result))
                        return result;
    
                    result = new object();
                    locks[index] = result;
                    return result;
                }
            }
        }
    }
    

    然后,要锁定int,只需(每次使用相同的同步器)

    lock (sync[15]) { ... }
    

    当给定相同的索引两次时,此类返回相同的锁对象。当一个新索引出现时,它创建一个对象,返回它,并将它存储在字典中以备下次使用。

    它可以很容易地更改为与任何 struct 或值类型,或 static 这样就不必传递synchronizer对象。

        2
  •  7
  •   Greg Beech    15 年前

    我倾向于研究基于数据库的锁定。最简单的方法是使用乐观锁定,比如最后更新post的时间戳,并拒绝对post进行的更新,除非时间戳匹配。

        3
  •  5
  •   skolima    13 年前

    我读过很多关于锁定对web应用程序不安全的评论,但是除了webfarms之外,我还没有看到任何解释。我想听听反对它的论据。

    我也有类似的需求,不过我正在硬盘上缓存重新调整大小的图像(这显然是一个本地操作,所以web场场景不是问题)。

    这里是@Configurator发布内容的重做版本。它包括一些@Configurator没有包括的功能:

    1. 泛型:允许基于不同的数据类型(如int或string)进行锁定。

    这是密码。。。

    /// <summary>
    /// Provides a way to lock a resource based on a value (such as an ID or path).
    /// </summary>
    public class Synchronizer<T>
    {
    
        private Dictionary<T, SyncLock> mLocks = new Dictionary<T, SyncLock>();
        private object mLock = new object();
    
        /// <summary>
        /// Returns an object that can be used in a lock statement. Ex: lock(MySync.Lock(MyValue)) { ... }
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public SyncLock Lock(T value)
        {
            lock (mLock)
            {
                SyncLock theLock;
                if (mLocks.TryGetValue(value, out theLock))
                    return theLock;
    
                theLock = new SyncLock(value, this);
                mLocks.Add(value, theLock);
                return theLock;
            }
        }
    
        /// <summary>
        /// Unlocks the object. Called from Lock.Dispose.
        /// </summary>
        /// <param name="theLock"></param>
        public void Unlock(SyncLock theLock)
        {
            mLocks.Remove(theLock.Value);
        }
    
        /// <summary>
        /// Represents a lock for the Synchronizer class.
        /// </summary>
        public class SyncLock
            : IDisposable
        {
    
            /// <summary>
            /// This class should only be instantiated from the Synchronizer class.
            /// </summary>
            /// <param name="value"></param>
            /// <param name="sync"></param>
            internal SyncLock(T value, Synchronizer<T> sync)
            {
                Value = value;
                Sync = sync;
            }
    
            /// <summary>
            /// Makes sure the lock is removed.
            /// </summary>
            public void Dispose()
            {
                Sync.Unlock(this);
            }
    
            /// <summary>
            /// Gets the value that this lock is based on.
            /// </summary>
            public T Value { get; private set; }
    
            /// <summary>
            /// Gets the synchronizer this lock was created from.
            /// </summary>
            private Synchronizer<T> Sync { get; set; }
    
        }
    
    }
    

    你可以这样使用它。。。

    public static readonly Synchronizer<int> sPostSync = new Synchronizer<int>();
    ....
    using(var theLock = sPostSync.Lock(myID))
    lock (theLock)
    {
        ...
    }
    
        4
  •  3
  •   Community Jaime Torres    7 年前

    我个人也会同意 Greg's Konrad's 接近。

    如果你真的想 lock 针对post ID本身(假设您的代码只在一个进程中运行),那么类似的事情就不是了 脏的:

    public class ModeratorUtils
    {
        private static readonly HashSet<int> _LockedPosts = new HashSet<int>();
    
        public void ModeratePost(int postId)
        {
            bool lockedByMe = false;
            try
            {
                lock (_LockedPosts)
                {
                    lockedByMe = _LockedPosts.Add(postId);
                }
    
                if (lockedByMe)
                {
                    // do your editing
                }
                else
                {
                    // sorry, can't edit at this time
                }
            }
            finally
            {
                if (lockedByMe)
                {
                    lock (_LockedPosts)
                    {
                        _LockedPosts.Remove(postId);
                    }
                }
            }
        }
    }
    
        5
  •  3
  •   Community Jaime Torres    7 年前

    此选项基于configurator提供的正确答案,并进行了以下修改:

    1. 防止字典的大小无法控制地增长。因为,新的帖子将获得新的ID,所以锁的字典将无限期地增长。解决方案是根据最大字典大小修改id。这确实意味着一些id将具有相同的锁(并且必须等待,否则就不必等待),但是对于某些字典大小来说,这是可以接受的。
    2. 使用ConcurrentDictionary,因此不需要单独的字典锁。

    代码:

    internal class IdLock
    {
        internal int LockDictionarySize
        {
            get { return m_lockDictionarySize; }
        }
        const int m_lockDictionarySize = 1000;
        ConcurrentDictionary<int, object> m_locks = new ConcurrentDictionary<int, object>();
        internal object this[ int id ]
        {
            get
            {
                object lockObject = new object();
                int mapValue = id % m_lockDictionarySize;
                lockObject = m_locks.GetOrAdd( mapValue, lockObject );
                return lockObject;
            }
        }
    }
    

    1. 根据允许的最大内部id字符串数修改id。
    2. 将此修改后的值转换为字符串。
    3. 为了名称冲突安全,将修改后的字符串与GUID或命名空间名称连接起来。
    4. 在这根绳子上实习。
    5. 锁定插入的绳子。 this answer 有关信息:

    字符串实习方法的唯一好处是不需要管理字典。我更喜欢以这种方式实习的方式继续实习。它还利用实习来完成一些从未打算/设计好的事情。

        6
  •  1
  •   Konrad Rudolph    15 年前

    你为什么不把整个帖子都锁在ID上呢?

        7
  •  1
  •   Faraz M. Khan    12 年前

    Coresystem at codeplex有两个基于值类型的线程同步类,有关详细信息,请参见 http://codestand.feedbook.org/2012/06/lock-on-integer-in-c.html

        8
  •  0
  •   Pontus Gagge    15 年前

    我怀疑您是否应该使用数据库或O/S级别的特性(如锁)来进行业务级别的决策。长时间持有锁会产生大量开销(在这些上下文中,任何超过几百毫秒的锁都是永恒的)。

    在帖子中添加状态字段。如果您直接处理多个therad,那么可以使用O/S级别锁来设置标志。

        9
  •  0
  •   Lasse V. Karlsen    15 年前

    你需要一个完全不同的方法。

    请记住,对于一个网站,您实际上没有一个实时运行的应用程序在另一端响应用户所做的事情。

    因此,您需要在应用程序将调节页面返回给调节器之后锁定以保持,然后在调节器完成后释放它。

    你需要处理一些超时,如果版主在收回审核页面后关闭了浏览器,因此从不与服务器通信,他/她已经完成了该帖子的审核过程。

        10
  •  0
  •   Sam Saffron James Allen    15 年前

        11
  •  0
  •   to StackOverflow    15 年前

    恰好具有相同值的两个装箱整数是完全独立的对象。

        12
  •  0
  •   Omar Kooheji    15 年前

    C#锁定是为了线程安全,它的工作方式与web应用程序不同。

    最简单的解决方案是向要锁定的表中添加一列,当somone锁定该列时,向db写入该列已锁定。

    如果列被锁定进行编辑,不要让任何人在编辑模式下打开文章。

    否则,请维护一个锁定条目ID的静态列表,并在允许编辑之前与之进行比较。

        13
  •  0
  •   Josh Smeaton    15 年前

    要确保删除不会发生两次吗?

    CREATE PROCEDURE RemovePost( @postID int )
    AS
        if exists(select postID from Posts where postID = @postID)
        BEGIN
            DELETE FROM Posts where postID = @postID
            -- Do other stuff
        END
    

    无论如何,这将适用于大多数情况。唯一失败的情况是,如果两个版主几乎完全同时提交,并且exists()函数在一个请求上传递,而DELETE语句正好在另一个请求上执行。我很乐意用这个小网站。您可以更进一步,检查delete是否确实删除了一行,然后再继续其余的操作,这将保证所有行的原子性。

    尝试创建一个锁定代码,对于这个用例,我认为非常不切实际。有两个版主试图删除一篇文章,一个成功,另一个没有效果,你什么都不会失去。

        14
  •  -1
  •   rguerreiro    15 年前

    您应该使用这样的同步对象:

    public class YourForm
    {
        private static object syncObject = new object();
    
        public void Moderate()
        {
            lock(syncObject)
            {
                // do your business
            }
        }
    }
    

    但是这种方法不应该在web应用程序场景中使用。