Why do we need locks for threads, if we have GIL?
<p>I believe it is a stupid question but I still can't find it. Actually it's better to separate it into two questions:</p>

<p>1) Am I right that we could have a lot of threads but because of GIL in one moment only one thread is executing?</p>

<p>2) If so, why do we still need locks? We use locks to avoid the case when two threads are trying to read/write some shared object, because of GIL twi threads can't be executed in one moment, can they?</p>

<p>GIL protects the Python interals. That means:</p>

<ol>
<li>you don't have to worry about something in the interpreter going wrong because of multithreading</li>
<li>most things do not really run in parallel, because python code is executed sequentially due to GIL</li>
</ol>

<p>But GIL does not protect your own code. For example, if you have this code:</p>

<pre><code>self.some_number += 1
</code></pre>

<p>That is going to read value of <code>self.some_number</code>, calculate <code>some_number+1</code> and then write it back to <code>self.some_number</code>.</p>

<p>If you do that in two threads, the operations (read, add, write) of one thread and the other may be mixed, so that the result is wrong.</p>

<p>This could be the order of execution:</p>

<ol>
<li>thread1 reads <code>self.some_number</code> (0)</li>
<li>thread2 reads <code>self.some_number</code> (0)</li>
<li>thread1 calculates <code>some_number+1</code> (1)</li>
<li>thread2 calculates <code>some_number+1</code> (1)</li>
<li>thread1 writes 1 to <code>self.some_number</code></li>
<li>thread2 writes 1 to <code>self.some_number</code></li>
</ol>

<p>You use locks to enforce this order of execution:</p>

<ol>
<li>thread1 reads <code>self.some_number</code> (0)</li>
<li>thread1 calculates <code>some_number+1</code> (1)</li>
<li>thread1 writes 1 to <code>self.some_number</code></li>
<li>thread2 reads <code>self.some_number</code> (1)</li>
<li>thread2 calculates <code>some_number+1</code> (2)</li>
<li>thread2 writes 2 to <code>self.some_number</code></li>
</ol>

<h2>EDIT: Let's complete this answer with some code which shows the explained behaviour:</h2>

<pre><code>import threading
import time

total = 0
lock = threading.Lock()

def increment_n_times(n):
    global total
    for i in range(n):
        total += 1

def safe_increment_n_times(n):
    global total
    for i in range(n):
        lock.acquire()
        total += 1
        lock.release()

def increment_in_x_threads(x, func, n):
    threads = [threading.Thread(target=func, args=(n,)) for i in range(x)]
    global total
    total = 0
    begin = time.time()
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()
    print('finished in {}s.\ntotal: {}\nexpected: {}\ndifference: {} ({} %)'
           .format(time.time()-begin, total, n*x, n*x-total, 100-total/n/x*100))
</code></pre>

<p>There are two functions which implement increment. One uses locks and the other does not.</p>

<p>Function <code>increment_in_x_threads</code> implements parallel execution of the incrementing function in many threads.</p>

<p>Now running this with a big enough number of threads makes it almost certain that an error will occur:</p>

<pre><code>print('unsafe:')
increment_in_x_threads(70, increment_n_times, 100000)

print('\nwith locks:')
increment_in_x_threads(70, safe_increment_n_times, 100000)
</code></pre>

<p>In my case, it printed:</p>

<pre><code>unsafe:
finished in 0.9840562343597412s.
total: 4654584
expected: 7000000
difference: 2345416 (33.505942857142855 %)

with locks:
finished in 20.564176082611084s.
total: 7000000
expected: 7000000
difference: 0 (0.0 %)
</code></pre>

<p>So without locks, there were many errors (33% of increments failed). On the other hand, with locks it was 20 time slower.</p>

<p>Of course, both numbers are blown up because I used 70 threads, but this shows the general idea.</p>

python multithreading