I'm studying 'Learning concurrency in python' which is written by Elliot Forbes. In this book, explaining 'Lists, by default, are thread safe, but only in the way that we access them. It's important to note that the data represented within this list structure is, in fact, not protected, and if you want to safely modify this data, then you must implement a proper locking mechanism to ensure that multiple threads can't potentially run into race conditions within their execution--this holds true for all thread-safe containers. The append() function is one of the few methods for our list data structure that is atomic, and, as such, thread-safe. Lists, thus, become a very quick and easy structure that we can leverage for temporary, in-memory storage. However, if we were to attempt to modify anything within this list in a concurrent fashion, then it's highly possible that we start to see the side effects most often attributed to race conditions. One prime example of such a side effect is if we, for instance, try to update the second element in our list at the same time as another thread. If one were to read, and subsequently write, at the same time as a competing thread, then we could see issues where the value is altered incorrectly. If you wish to utilize lists within your multithreaded applications, then you can do so by extending the class in a similar fashion to how we've previously extended the set primitive.'
So, i used decorator because this book explained using decorator. But, i couldn't find any difference between using no locked list.insert() and using decorated lock.
in 1) : t1's result is 10000. t2's result is 20000. Final result is 20000. I thought final result should be less than 20000 because of race conditions.
in 2) : same with 1)'s result.
- Here is a code using no lock.
'''
from threading import Lock
import threading
list = []
def work():
global list
for i in range(10000):
list.insert(i, i)
print("{} is completed, list length = {}".format(threading.get_ident(), len(list)))
print(list)
threads = []
t1 = threading.Thread(target=work)
t1.start()
t2 = threading.Thread(target=work)
t2.start()
threads.append(t1)
threads.append(t2)
for t in threads:
t.join()
print("final list length = ", len(list))
print(list)
'''
- Here is a code of decorated version.
'''
from threading import Lock
import threading
def locked_method(method):
def new_method(self, *args, **kwargs):
with self._lock:
return method(self, *args, **kwargs)
return new_method
class Locked_list(list):
def __init__(self, *args, **kwargs):
self._lock = Lock()
super(Locked_list, self).__init__(*args, **kwargs)
@locked_method
def remove(self, *args, **kwargs):
return super(Locked_list, self).remove(elem)
@locked_method
def insert(self, *args, **kwargs):
return super(Locked_list, self).insert(index, elem)
list = []
def work():
global list
for i in range(10000):
list.insert(i, i)
print("{} is completed, list length = {}".format(threading.get_ident(), len(list)))
print(list)
threads = []
t1 = threading.Thread(target=work)
t1.start()
t2 = threading.Thread(target=work)
t2.start()
threads.append(t1)
threads.append(t2)
for t in threads:
t.join()
print("final list length = ", len(list))
print(list)
'''
Is my code wrong? or python's list doesn't have any race conditions?