Lably
Skip to content

Lock (computer science)

Synchronization mechanism for enforcing limits on access to a resource

  • 16 min read
  • 7 chapters

Related on Lably

Through Computer science, Mutual exclusion and Exception handling

Types

Generally, locks are advisory locks, where each thread cooperates by acquiring the lock before accessing the corresponding data. Some systems also implement mandatory locks, where attempting unauthorized access to a locked resource will force an exception in the entity attempting to make the access.

The simplest type of lock is a binary semaphore. It provides exclusive access to the locked data. Other schemes also provide shared access for reading data. Other widely implemented access modes are exclusive, intend-to-exclude and intend-to-upgrade.

Another way to classify locks is by what happens when the lock strategy prevents the progress of a thread. Most locking designs block the execution of the thread requesting the lock until it is allowed to access the locked resource. With a spinlock, the thread simply waits ("spins") until the lock becomes available. This is efficient if threads are blocked for a short time, because it avoids the overhead of operating system process rescheduling. It is inefficient if the lock is held for a long time, or if the progress of the thread that is holding the lock depends on preemption of the locked thread.

Locks typically require hardware support for efficient implementation. This support usually takes the form of one or more atomic instructions such as "test-and-set", "fetch-and-add" or "compare-and-swap". These instructions allow a single process to test if the lock is free, and if free, acquire the lock in a single atomic operation.

Uniprocessor architectures have the option of using uninterruptible sequences of instructions—using special instructions or instruction prefixes to disable interrupts temporarily—but this technique does not work for multiprocessor shared-memory machines. Proper support for locks in a multiprocessor environment can require quite complex hardware or software support, with substantial synchronization issues.

The reason an atomic operation is required is because of concurrency, where more than one task executes the same logic. For example, consider the following C code:

<span id="mwKg"></span><span class="k" id="mwKw">if</span><span class="w" id="mwLA"> </span><span class="p" id="mwLQ">(</span><span class="n" id="mwLg">lock</span><span class="w" id="mwLw"> </span><span class="o" id="mwMA">==</span><span class="w" id="mwMQ"> </span><span class="mi" id="mwMg">0</span><span class="p" id="mwMw">)</span><span class="w" id="mwNA"> </span><span class="p" id="mwNQ">{</span> <span class="w" id="mwNg"> </span><span class="c1" id="mwNw">// If lock free, set it</span> <span class="w" id="mwOA"> </span><span class="n" id="mwOQ">lock</span><span class="w" id="mwOg"> </span><span class="o" id="mwOw">=</span><span class="w" id="mwPA"> </span><span class="n" id="mwPQ">pid</span><span class="p" id="mwPg">;</span> <span class="p" id="mwPw">}</span>

The above example does not guarantee that the task has the lock, since more than one task can be testing the lock at the same time. Since both tasks will detect that the lock is free, both tasks will attempt to set the lock, not knowing that the other task is also setting the lock. Dekker's or Peterson's algorithm are possible substitutes if atomic locking operations are not available.

Careless use of locks can result in deadlock or livelock. A number of strategies can be used to avoid or recover from deadlocks or livelocks, both at design-time and at run-time. (The most common strategy is to standardize the lock acquisition sequences so that combinations of inter-dependent locks are always acquired in a specifically defined "cascade" order.)

Some languages do support locks syntactically. An example in C# follows:

<span id="mwSw"></span><span class="k" id="mwTA">namespace</span><span class="w" id="mwTQ"> </span><span class="nn" id="mwTg">Wikipedia.Examples</span><span class="p" id="mwTw">;</span> <span class="k" id="mwUA">using</span><span class="w" id="mwUQ"> </span><span class="nn" id="mwUg">System.Threading</span><span class="p" id="mwUw">;</span> <span class="k" id="mwVA">public</span><span class="w" id="mwVQ"> </span><span class="k" id="mwVg">class</span><span class="w" id="mwVw"> </span><span class="nc" id="mwWA">Account</span><span class="w" id="mwWQ"> </span><span class="c1" id="mwWg">// This is a monitor of an account</span> <span class="p" id="mwWw">{</span> <span class="w" id="mwXA"> </span><span class="c1" id="mwXQ">// Use `object` in versions earlier than C# 13</span> <span class="w" id="mwXg"> </span><span class="k" id="mwXw">private</span><span class="w" id="mwYA"> </span><span class="k" id="mwYQ">readonly</span><span class="w" id="mwYg"> </span><span class="n" id="mwYw">Lock</span><span class="w" id="mwZA"> </span><span class="n" id="mwZQ">_balanceLock</span><span class="w" id="mwZg"> </span><span class="o" id="mwZw">=</span><span class="w" id="mwaA"> </span><span class="k" id="mwaQ">new</span><span class="p" id="mwag">();</span> <span class="w" id="mwaw"> </span><span class="k" id="mwbA">private</span><span class="w" id="mwbQ"> </span><span class="kt" id="mwbg">decimal</span><span class="w" id="mwbw"> </span><span class="n" id="mwcA">_balance</span><span class="w" id="mwcQ"> </span><span class="o" id="mwcg">=</span><span class="w" id="mwcw"> </span><span class="mi" id="mwdA">0</span><span class="p" id="mwdQ">;</span> <span class="w" id="mwdg"> </span><span class="k" id="mwdw">public</span><span class="w" id="mweA"> </span><span class="k" id="mweQ">void</span><span class="w" id="mweg"> </span><span class="nf" id="mwew">Deposit</span><span class="p" id="mwfA">(</span><span class="kt" id="mwfQ">decimal</span><span class="w" id="mwfg"> </span><span class="n" id="mwfw">amount</span><span class="p" id="mwgA">)</span> <span class="w" id="mwgQ"> </span><span class="p" id="mwgg">{</span> <span class="w" id="mwgw"> </span><span class="c1" id="mwhA">// Only one thread at a time may execute this statement.</span> <span class="w" id="mwhQ"> </span><span class="k" id="mwhg">lock</span><span class="w" id="mwhw"> </span><span class="p" id="mwiA">(</span><span class="n" id="mwiQ">_balanceLock</span><span class="p" id="mwig">)</span> <span class="w" id="mwiw"> </span><span class="p" id="mwjA">{</span> <span class="w" id="mwjQ"> </span><span class="n" id="mwjg">_balance</span><span class="w" id="mwjw"> </span><span class="o" id="mwkA">+=</span><span class="w" id="mwkQ"> </span><span class="n" id="mwkg">amount</span><span class="p" id="mwkw">;</span> <span class="w" id="mwlA"> </span><span class="p" id="mwlQ">}</span> <span class="w" id="mwlg"> </span><span class="p" id="mwlw">}</span> <span class="w" id="mwmA"> </span><span class="k" id="mwmQ">public</span><span class="w" id="mwmg"> </span><span class="k" id="mwmw">void</span><span class="w" id="mwnA"> </span><span class="nf" id="mwnQ">Withdraw</span><span class="p" id="mwng">(</span><span class="kt" id="mwnw">decimal</span><span class="w" id="mwoA"> </span><span class="n" id="mwoQ">amount</span><span class="p" id="mwog">)</span> <span class="w" id="mwow"> </span><span class="p" id="mwpA">{</span> <span class="w" id="mwpQ"> </span><span class="c1" id="mwpg">// Only one thread at a time may execute this statement.</span> <span class="w" id="mwpw"> </span><span class="k" id="mwqA">lock</span><span class="w" id="mwqQ"> </span><span class="p" id="mwqg">(</span><span class="n" id="mwqw">_balanceLock</span><span class="p" id="mwrA">)</span> <span class="w" id="mwrQ"> </span><span class="p" id="mwrg">{</span> <span class="w" id="mwrw"> </span><span class="n" id="mwsA">_balance</span><span class="w" id="mwsQ"> </span><span class="o" id="mwsg">-=</span><span class="w" id="mwsw"> </span><span class="n" id="mwtA">amount</span><span class="p" id="mwtQ">;</span> <span class="w" id="mwtg"> </span><span class="p" id="mwtw">}</span> <span class="w" id="mwuA"> </span><span class="p" id="mwuQ">}</span> <span class="p" id="mwug">}</span>

C# introduced System.Threading.Lock in C# 13 on .NET 9.

The code lock(this) can lead to problems if the instance can be accessed publicly.

Similar to Java, C# can also synchronize entire methods, by using the MethodImplOptions.Synchronized attribute.

<span id="mw0g"></span><span class="na" id="mw0w">[MethodImpl(MethodImplOptions.Synchronized)]</span> <span class="k" id="mw1A">public</span><span class="w" id="mw1Q"> </span><span class="k" id="mw1g">void</span><span class="w" id="mw1w"> </span><span class="nf" id="mw2A">SomeMethod</span><span class="p" id="mw2Q">()</span> <span class="p" id="mw2g">{</span> <span class="w" id="mw2w"> </span><span class="c1" id="mw3A">// do stuff</span> <span class="p" id="mw3Q">}</span>

Sign in to keep reading

6 more chapters are free with a Lably account.

More in Computing and communication