Why use Threading
- Use threading for IO bound task, or consider Asyncio.
- Not suitable for CPU bound task due to GIL and utilizing single core only. Consider Multiprocessing for CPU bound task.
- Can use shared variable for communication between threads, probably require a lock or thread-safe queue.
- Very hard to write and maintain correctness of code: think again before attempting a complex solution with threading.
NOTE: Refer Python Threading vs Multiprocessing vs Asyncio.
Launch a thread.
import threadingdef run(): print('run')threading.Thread(target=run).start()
Pass named argument into thread
def run(name): print(f"run: {name}")threading.Thread(target=run).start(kwargs={'name': 'test'})
Deamon thread (infinite loop)
import threadingimport timeis_running = Truedef run(): while is_running: print('.', end='', flush=True) time.sleep(1)t = threading.Thread(target=run)t.daemon = Truet.start()del t# stop daemon thread after 10stime.sleep(10)is_running = False
Shared variable
Atomic operation should be thread-safe
import timeimport randomimport threadingcurrent_time = 0is_loop = Truedef run(): global current_time time.sleep(random.random() * 0.1) current_time = time.time()def print_time(): last_time = 0 count = 0 while is_loop: if current_time != last_time: count += 1 print(f"{count}={current_time}") last_time = current_time print('print_time end: ')for i in range(10): threading.Thread(target=run).start()t = threading.Thread(target=print_time)t.daemon = Truet.start()del ttime.sleep(2)print(f'stop print_time')is_loop = False
As you have notice above, print_time
access to current_time
is not truely atomic. We are doing checking (current_time != last_time
) followed by printing (print(f"{count}={current_time}")
) where the value could have changed in between these operation.
A truly atomic print_time
access to current_time
would be:
def print_time(): while True: print(f"{current_time}")
NOTE: It is still possible to miss out printing one or two changes to current_time
, as current_time
could changed twice before print_time
is executed.
Lock
- Acquire a lock just before assign the value (prevent other thread to assign a new value)
- Release the lock after printing the value (allow other thread to assign a new value)
import timeimport randomimport threadingcurrent_time = 0current_time_lock = threading.Lock()is_loop = Truedef run(): global current_time time.sleep(random.random() * 0.1) current_time_lock.acquire() current_time = time.time()def print_time(): last_time = 0 count = 0 while is_loop: if current_time != last_time: count += 1 print(f"{count}={current_time}") last_time = current_time current_time_lock.release() print('print_time end: ')for i in range(10): threading.Thread(target=run).start()t = threading.Thread(target=print_time)t.daemon = Truet.start()del ttime.sleep(2)print(f'stop print_time')is_loop = False
Queue
Python queue is thread-safe.
import timeimport randomimport threadingimport queuecurrent_time_queue = queue.Queue()is_loop = Truedef run(): time.sleep(random.random() * 0.1) current_time = time.time() current_time_queue.put(current_time)def print_time(): count = 0 while is_loop: current_time = current_time_queue.get() if current_time != None: count += 1 print(f"{count}={current_time}") print('print_time end: ')for i in range(10): threading.Thread(target=run).start()t = threading.Thread(target=print_time)t.daemon = Truet.start()del ttime.sleep(2)print(f'stop print_time')is_loop = False