For a supervisor-like project, I use the threading library to manage some child process. At some point, the user can prompt command to send instructions to the process management thread. These commands are stored in a Queue object shared between the main process and process management thread. I thought I'll need mutex to solve concurrency issues so I made a a little script to try it out, but without mutex first to be sure I get the expected concurrency issue.
I expected from the script to print a messy list of int every second:
import threading import time def longer(l, mutex=None): while 1: last_val = l[-1] l.append(last_val + 1) time.sleep(1) return dalist = [0] t = threading.Thread(target=longer, args=(dalist,)) t.daemon = True t.start() while 1: last_val = dalist[-1] dalist.append(last_val + 1) print dalist time.sleep(1) But in fact it print a nice list of following int like these:
[0, 1, 2] [0, 1, 2, 3] [0, 1, 2, 3, 4, 5, 6] From this answer in another post I thought it come from the threading library, so I did the same with the multiprocessing lib:
import multiprocessing as mp import time def longer(l, mutex=None): while 1: last_val = l[-1] l.append(last_val + 1) time.sleep(1) return dalist = [0] t = mp.Process(target=longer, args=(dalist,)) t.start() while 1: last_val = dalist[-1] dalist.append(last_val + 1) print dalist time.sleep(1) But I got the same result, a bit 'slower' :
[0, 1] [0, 1, 2] [0, 1, 2, 3] [0, 1, 2, 3, 4] So I wonder do I really need mutex to manage a Queue-like object shared between thread ??? And also, from one of the code above, how could I effectively reproduce the expected concurrency issue I search for ?
Thanks for reading
Edit 1: From the remarks of user4815162342 I change the first snippet and I manage to have some sort of race condition by moving the sleep call inside the 'longer' function between value retrieval and list appending:
import threading import time def longer(l, mutex=None): while 1: last_val = l[-1] time.sleep(1) l.append(last_val + 1) return dalist = [0] t = threading.Thread(target=longer, args=(dalist,)) t.daemon = True t.start() while 1: last_val = dalist[-1] dalist.append(last_val + 1) print dalist time.sleep(1) which give me stuffs like that:
[0, 1] [0, 1, 1, 2] [0, 1, 1, 2, 2, 3] [0, 1, 1, 2, 2, 3, 3, 4] and I manage to solve my artificial issue using threading Lock like that:
import threading import time def longer(l, mutex=None): while 1: if mutex is not None: mutex.acquire() last_val = l[-1] time.sleep(1) l.append(last_val + 1) if mutex is not None: mutex.release() return dalist = [0] mutex = threading.Lock() t = threading.Thread(target=longer, args=(dalist, mutex)) t.daemon = True t.start() while 1: if mutex is not None: mutex.acquire() last_val = dalist[-1] dalist.append(last_val + 1) if mutex is not None: mutex.release() print dalist time.sleep(1) which then produce:
[0, 1, 2] [0, 1, 2, 3, 4, 5] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
withstatement rather than explicit calls toacquireandreleasemethods. Also, one rarely needs to pass the mutex to the function - a mutex protecting a global resource will reside in a global variable, and a mutex protecting an object-level resource will reside in the object and be reachable asself.some_attribute.