24

Is there any way to lock on an integer in C#? Integers can not be used with lock because they are boxed (and lock only locks on references).

The scenario is as follows: I have a forum based website with a moderation feature. What I want to do is make sure that no more than one moderator can moderate a post at any given time. To achieve this, I want to lock on the ID of the post.

I've had a couple of ideas so far (e.g. using a dictionary<int, object>), but I'm looking for a better and cleaner way.

Any suggestions?

Omu
  • 69,856
  • 92
  • 277
  • 407
Waleed Eissa
  • 10,283
  • 16
  • 60
  • 82
  • 1
    What is the lock for? If the edits happen one after the other (which is what a lock would cause), you'd gain nothing. All DB updates are implicitly transactional, meaning they happen one after the other and don't mix. – configurator Apr 23 '09 at 11:25
  • it's a little complicated to explain but in short after locking, I update multiple databases (very possibly running on multiple DB servers), e.g. delete the post (in some database), update the moderation database (if the item was reported .. etc), even in the moderation database, I delete records from tables and insert records into other tables, this could cause another request trying to do the same stuff to throw an exception, it all has to be done at once and the other requests should wait – Waleed Eissa Apr 23 '09 at 11:32
  • 2
    You are using the wrong tool for the Job, if you want to stop someone editing a record with a particular Id you need to keep a list of the prohibited ID's somewhere. you cannot lock the number 1672 and stop anything else from using it thats just madness. You can however check to see if record 1672 has been locked by another user and wait till that is done, or feed back that the operation coudn't be completed because somone else has a lock on the record. – Omar Kooheji Apr 23 '09 at 12:05
  • Actually I don't care if the user edits the post or not, it will be deleted by the moderator, what I'm trying to do is ensure that only one moderator can delete a post at a time (this involves updates to other tables as I explained in the comment above) – Waleed Eissa Apr 23 '09 at 12:15
  • hmm, maybe you could create a list of the posts currently being edited and check that? – Botz3000 Apr 23 '09 at 11:00
  • Actually this is what I meant when I mentioned using a dictionary, but I believe this can get messy very easily – Waleed Eissa Apr 23 '09 at 11:37
  • It sounds like you need to use a distributed transaction. If your databases support them, it will work a lot better than trying to implement a lock in your application logic. – Andrew Rondeau May 22 '18 at 22:18

15 Answers15

30

I like doing it like this

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;
            }
        }
    }
}

Then, to lock on an int you simply (using the same synchronizer every time)

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

This class returns the same lock object when given the same index twice. When a new index comes, it create an object, returning it, and stores it in the dictionary for next times.

It can easily be changed to work generically with any struct or value type, or to be static so that the synchronizer object does not have to be passed around.

configurator
  • 40,828
  • 14
  • 81
  • 115
  • If you make it static, keep in mind indexers in C# can't declared as static, so just make it a method instead, worked great for me, thanks Configurator – Myster Oct 22 '09 at 20:49
  • 2
    Also, keep in mind that if you make it static, it's never going to be garbage collected, and it could create a sort of memory leak - for each int you've ever locked on, there's going to be an object in the heap. – configurator Dec 19 '12 at 15:19
  • think in `this[int index]` you could try to get the result outside of the lock first, and than one more time inside – Omu Mar 05 '13 at 23:36
  • 4
    @omu: not in .Net 3. Dictionaries aren't thread safe even for just reading. In .Net 4, you can use a ConcurrentDictionary and avoid the lock altogether by calling GetOrAdd. – configurator Mar 06 '13 at 10:21
  • 2
    If you replace the Dictionary with a MemoryCache object you can use the absolute expiration parameter to put an expiration on the values (to avoid the dictionary growing indefinitely). – Joshua Sep 12 '14 at 19:16
  • 3
    @Joshua: You'd have to make sure the values don't expire while they're locked though. – configurator Sep 12 '14 at 23:01
  • Wow it's evil! Add another if (locks.TryGetValue(index, out result)) outside of lock() The way it is now it locks even to read, so it's basically a forced bottleneck among all calls – Igor Be Apr 07 '16 at 17:45
  • @IgorBe as it currently is the read lock is extremely short lived, so this shouldn't be a bottleneck. Adding the extra if means you're accessing the dictionary outside the lock and could result in corrupted data. – configurator Apr 07 '16 at 18:04
  • You have two options for the cleanup, set a count at which point the dictionary removes all items. Or count usages of the dictionary's id's. On the last usage of an id, remove the id from the dictionary. This is quite complex and you probably want both methods to be safe. You definitely need garbage collection depending on if you are keeping a long running process with many records which I'd expect with threading. – Timothy Gonzalez Oct 11 '16 at 18:50
  • 1
    Both of these would be dangerously hard to write correctly. And the hard part is you won't know if you've made a mistake, you'll just see seemingly random threading issues when the process is long running. – configurator Oct 11 '16 at 20:41
7

If it's a website then using an in-process lock probably isn't the best approach as if you need to scale the site out onto multiple servers, or add another site hosting an API (or anything else that would require another process accessing the same data to exist) then all your locking strategies are immediately ineffective.

I'd be inclined to look into database-based locking for this. The simplest approach is to use optimistic locking with something like a timestamp of when the post was last updated, and to reject updates made to a post unless the timestamps match.

Greg Beech
  • 133,383
  • 43
  • 204
  • 250
  • Actually I thought about this but I'm doing this temporarily, if the site is to run on a server farm anytime, I'll use some distributed locking mechanism then. – Waleed Eissa Apr 23 '09 at 11:18
  • Just to add, on the database side it's an sproc which updates multiple tables (MyISAM tables in MySQL), I still haven't figured out how to block other requests trying to execute the same sproc until it finishes, not sure how this can be done or even if it can be done at all. – Waleed Eissa Apr 23 '09 at 11:27
  • Not sure how to do this type of thing in MySQL I'm afraid. In SQL Server you could use sp_getapplock so there may be something similar in MySQL to this. Alternatively, don't block the requests, just allow them and raise an error if the timestamps don't match. – Greg Beech Apr 23 '09 at 11:40
5

I've read a lot of comments mentioning that locking isn't safe for web applications, but, other than web farms, I haven't seen any explanations of why. I would be interested in hearing the arguments against it.

I have a similar need, though I'm caching re-sized images on the hard drive (which is obviously a local action so a web farm scenario isn't an issue).

Here is a redone version of what @Configurator posted. It includes a couple features that @Configurator didn't include:

  1. Unlocking: Ensures the list doesn't grow unreasonably large (we have millions of photos and we can have many different sizes for each).
  2. Generic: Allows locking based on different data types (such as int or string).

Here's the code...

/// <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; }

    }

}

Here's how you can use it...

public static readonly Synchronizer<int> sPostSync = new Synchronizer<int>();
....
using(var theLock = sPostSync.Lock(myID))
lock (theLock)
{
    ...
}
Brian
  • 37,399
  • 24
  • 94
  • 109
  • Won't work. T1 locks on new ID. T2 tries to lock on the same ID and waits while T1 is holding it. T1 releases the lock and dispose removes it from dictionary. T2 enters lock. While T2 holds the lock T3 tries to acquire the lock on the same ID... and it gets one simultaneously! – HolisticElastic Dec 01 '14 at 08:04
  • @archimed7592 good catch. This has been working adequately in a high volume system for years, but I do see the problem and we might just not have seen the issue. I think this can be improved with lock counting so if somebody else gets the lock it won't remove it until the count is 0. I'll update the code when I get the chance. – Brian Dec 01 '14 at 17:08
  • Using locks in a web application seems like a good idea, if you know you have a single server, but your application will not scale. If you do end up creating an application of value and it gets placed on multiple servers then issues will be introduced that are hard to detect. Features that you expect to work and have depended on will suddenly fail. When you make a web application you have no control over how it will be delivered in the future. – Timothy Gonzalez Aug 03 '16 at 20:51
  • Locking works great as long as you are only worried about local resources, such as files (I mentioned this in my answer). We use something very similar to the code I provided in my answer to restrict access to files in our production web servers. We have 6 web servers in the farm (though the number doesn't matter since we only care about the local machine) processing millions of requests per day. This has been running for years without any issues. – Brian Aug 03 '16 at 22:23
4

This option builds on the good answer provided by configurator with the following modifications:

  1. Prevents the size of the dictionary from growing uncontrollably. Since, new posts will get new ids, your dictionary of locks will grow indefinitely. The solution is to mod the id against a maximum dictionary size. This does mean that some ids will have the same lock (and have to wait when they would otherwise not have to), but this will be acceptable for some dictionary size.
  2. Uses ConcurrentDictionary so there is no need for a separate dictionary lock.

The code:

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;
        }
    }
}

Also, just for completeness, there is the alternative of string interning: -

  1. Mod the id against the maximum number of interned id strings you will allow.
  2. Convert this modded value to a string.
  3. Concatenate the modded string with a GUID or namespace name for name collision safety.
  4. Intern this string.
  5. lock on the interned string. See this answer for some information:

The only benefit of the string interning approach is that you don't need to manage a dictionary. I prefer the dictionary of locks approach as the intern approach makes a lot of assumptions about how string interning works and that it will continue to work in this way. It also uses interning for something it was never meant / designed to do.

Community
  • 1
  • 1
acarlon
  • 16,764
  • 7
  • 75
  • 94
3

I would personally go with either Greg's or Konrad's approach.

If you really do want to lock against the post ID itself (and assuming that your code will only ever be running in a single process) then something like this isn't too dirty:

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);
                }
            }
        }
    }
}
Community
  • 1
  • 1
LukeH
  • 263,068
  • 57
  • 365
  • 409
1

Coresystem at codeplex has two class for thread synchronization based on value types, for details see http://codestand.feedbook.org/2012/06/lock-on-integer-in-c.html

Faraz M. Khan
  • 159
  • 1
  • 4
1

Why don't you lock on the whole posting instead just on its ID?

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    That assumes he has some posting class he can lock (which would be nice OO) but it could be that the id is just an input param for DB data, at which point I guess the lock should be at the DB level, so circularly if you can lock at all, do it against the posting instance :) – annakata Apr 23 '09 at 10:54
  • Nice answer annakata, you saved me the time :) .. it's just a param as you indicated – Waleed Eissa Apr 23 '09 at 11:20
  • If loaded from the DB, they will have different references so I can't lock on this – Waleed Eissa Apr 23 '09 at 11:34
  • Given your scenario, I'd use Greg's suggestion. That said, you could always wrap the loading of your records so that for any given posting only one object is ever created and subsequently reused (this works by keeping a local cache of objects in a dictionary). – Konrad Rudolph Apr 23 '09 at 12:32
0

You want to make sure that a delete doesn't happen twice?

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

This is pretty much SQL server syntax, I'm not familiar with MyISAM. But it allows stored procedures. I'm guessing you can mock up a similar procedure.

Anyhow, this will work for the majority of cases. The only time it will fail is if two moderators submit at almost exactly the same time, and the exists() function passes on one request just before the DELETE statement executes on another request. I would happily use this for a small site. You could take it a step further and check that the delete actually deleted a row before continuing with the rest, which would guarantee the atomicity of it all.

Trying to create a lock in code, for this use case, I consider very impractical. You lose nothing by having two moderators attempting to delete a post, with one succeeding, and the other having no effect.

Josh Smeaton
  • 47,939
  • 24
  • 129
  • 164
  • Thanks Josh but actually this is not exactly what I'm trying to do. If it was only a delete operation there wouldn't be a problem because the table is locked anyway by MySQL while deleting (any operation that changes the data in a table - ie. DELETE, UPDATE and INSERT - is atomic in MySQL and every other RDBMS). I have a set of related operations that updates, deletes and inserts in multiple tables and this is why I'm trying to use locking, actually there's a statement in MySQL for locking (it also supports time limited locks) but I'm trying to avoid doing it this way. – Waleed Eissa Oct 22 '09 at 11:41
  • It's hard to explain all the details in here and this is why my question was only about what I finally want to accomplish. – Waleed Eissa Oct 22 '09 at 11:47
0

I doubt you should use a database or O/S level feature such as locks for a business level decision. Locks incur significant overheads when held for long times (and in these contexts, anything beyond a couple of hundred milliseconds is an eternity).

Add a status field to the post. If you deal with several therads directly, then you can use O/S level locks -- to set the flag.

Pontus Gagge
  • 17,166
  • 1
  • 38
  • 51
0

You need a whole different approach to this.

Remember that with a website, you don't actually have a live running application on the other side that responds to what the user does.

You basically start a mini-app, which returns the web-page, and then the server is done. That the user ends up sending some data back is a by-product, not a guarantee.

So, you need to lock to persist after the application has returned the moderation page back to the moderator, and then release it when the moderator is done.

And you need to handle some kind of timeout, what if the moderator closes his browser after getting the moderation page back, and thus never communicates back with the server that he/she is done with the moderation process for that post.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • Note sure what this means, it's a static method so I sure can use locking inside it e.g. public static void ModeratePost(int postID, ModerationAction action) { // lock on the post id } It's called from a webservice as the moderation system uses Ajax – Waleed Eissa Apr 23 '09 at 12:06
  • So you only need to lock as part of the webservice call, and unlock before you return? In that case, a simple database lock would suffice, wouldn't it? – Lasse V. Karlsen Apr 23 '09 at 12:19
  • In that method, ModeratePost(), I delete the post (in some database) and I execute an sproc in another database, I don't want any other thread to execute that sproc while it's still running, after it finishes it will have deleted all the records that are related to that action, so if another thread executes the sproc it won't have any records to work on and it will just return, but if it executes the sproc while it's still running, this could affect the integrity of the data ... – Waleed Eissa Apr 23 '09 at 14:49
0

Ideally you can avoid all the complex and brittle C# locking and replace it with database locking, if your transactions are designed correctly then you should be able to get by with DB transactions only.

Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • Unfortunately I'm using MySQL with MyISAM tables, so no transactions – Waleed Eissa Apr 23 '09 at 11:51
  • My concern is, what happens when you scale and have multiple app domains running against a single DB, how are you going to manage access to the DB (who know maybe YAGNI) – Sam Saffron Apr 23 '09 at 12:08
  • I will probably get rid of MySQL then :) .. but seriously, I have an sproc for moderation that inserts records into tables and deletes records from other tables, as far as I understand (and please correct me if I'm wrong, also forgive me my poor understanding), transaction are used to ensure that all the statements will be executed or none of them will, I want something that will block other clients until the transaction finishes, can this be done with transactions? – Waleed Eissa Apr 23 '09 at 12:34
  • The point is that the details of the post to be moderated exist in some table, there are other tables that contain the reporting details .. etc, the sproc deletes all this and inserts them into other tables (history tables, just used for archiving), when the sproc finishes there will be nothing to do (all records are deleted), so if I can manage to block all other clients until the sproc finishes they will have nothing to do and just return, this is exactly what I'm trying to achieve .. – Waleed Eissa Apr 23 '09 at 12:39
0

Two boxed integers that happen to have the same value are completely indepent objects. So if you wanted to do this, your idea of Dictionary would probably be the way to go. You'd need to synchronize access to the dictionary to make sure you are always getting the same instance. And you'd have the problem of the dictionary growing in size.

Joe
  • 122,218
  • 32
  • 205
  • 338
0

C# locking is for thread safety and doesn't work the way you want it to for web applications.

The simplest solution is adding a column to the table that you want to lock and when somone locks it write to the db that that column is locked.

Dont let anyone open a post in edit mode if the column is locked for editing.

Otherwise maintain a static list of locked entry Ids and compare to that before allowing an edit.

Omar Kooheji
  • 54,530
  • 68
  • 182
  • 238
  • hmmm, I don't see why not, two requests mean two different threads, I don't see a problem with that .. are you saying that lock is never used in web applications? – Waleed Eissa Apr 23 '09 at 12:10
  • If you do this, make sure your read-lock and add-lock-flag is one atomic action. – Myster Oct 22 '09 at 20:13
-1
public static class ConexoesDeTeste
{
    private static int NumeroDeConexoes = 0;

    public static void Incrementar()
    {
         Interlocked.Increment(ref NumeroDeConexoes);
    }
    public static void Decrementar()
    {
        Interlocked.Decrement(ref NumeroDeConexoes);
    }

    public static int Obter() => NumeroDeConexoes;
}
-1

You should use a sync object like this:

public class YourForm
{
    private static object syncObject = new object();

    public void Moderate()
    {
        lock(syncObject)
        {
            // do your business
        }
    }
}

But this approach shouldn't be used in a web app scenario.

rguerreiro
  • 2,673
  • 3
  • 22
  • 23