Fundamentals 16 min read

Python Threading: Concepts, Creation Methods, Daemon Threads, and Synchronization with Locks

This article explains the fundamentals of Python threading, covering thread definitions, the threading module, two ways to create threads, daemon thread behavior, common thread‑safety issues, and how to use mutex locks to synchronize access to shared resources.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Python Threading: Concepts, Creation Methods, Daemon Threads, and Synchronization with Locks

Threads, also called lightweight processes, are the smallest unit of execution flow in a program, consisting of a thread ID, program counter, registers, and a stack, and share the resources of their parent process.

Python provides two standard library modules for threading: _thread (low‑level) and threading (high‑level). The article focuses on the threading module, which is used for most real‑world development.

Creating a Thread object follows the syntax threading.Thread(target=None, name=None, args=()) . Important parameters are target (the function to run), name (thread name), and args (function arguments as a tuple). Common methods include start() , run() , join() , isAlive() , getName() , and setName() .

<code>import threading
threading.Thread(target=None, name=None, args=())</code>

Two ways to start threads are demonstrated:

1) Function‑based creation – pass a function and its arguments to Thread , start the threads, and optionally join them.

<code>import threading, time, random, math

def printNum(idx):
    for num in range(idx):
        print("{0}\tnum={1}".format(threading.current_thread().getName(), num))
        delay = math.ceil(random.random()*2)
        time.sleep(delay)

if __name__ == '__main__':
    th1 = threading.Thread(target=printNum, args=(2,), name="thread1")
    th2 = threading.Thread(target=printNum, args=(3,), name="thread2")
    th1.start()
    th2.start()
    th1.join()
    th2.join()
    print("{0} 线程结束".format(threading.current_thread().getName()))</code>

2) Subclassing threading.Thread – override the run() method to define thread behavior.

<code>import threading, time, random, math

class MultiThread(threading.Thread):
    def __init__(self, threadName, num):
        super().__init__(name=threadName)
        self.num = num
    def run(self):
        for i in range(self.num):
            print("{0} i={1}".format(threading.current_thread().getName(), i))
            delay = math.ceil(random.random()*2)
            time.sleep(delay)

if __name__ == '__main__':
    thr1 = MultiThread("thread1", 3)
    thr2 = MultiThread("thread2", 2)
    thr1.start()
    thr2.start()
    thr1.join()
    thr2.join()
    print("{0} 线程结束".format(threading.current_thread().getName()))</code>

Daemon threads are explained: a daemon thread terminates automatically when the main (non‑daemon) thread finishes. Daemon status must be set before calling start() . The article shows examples where daemon threads may or may not complete before program exit.

<code>import threading, time

def run(taskName):
    print("任务:", taskName)
    time.sleep(2)
    print("{0} 任务执行完毕".format(taskName))

if __name__ == '__main__':
    for i in range(3):
        thr = threading.Thread(target=run, args=(f"task-{i}",))
        thr.setDaemon(True)
        thr.start()
    print("{0}线程结束,当线程数量={1}".format(threading.current_thread().getName(), threading.active_count()))
    print("消耗时间:", time.time() - start_time)</code>

The article then discusses thread‑safety problems caused by shared global variables and demonstrates how race conditions can produce incorrect results when multiple threads modify the same variable without protection.

<code>balance = 100

def change(num, counter):
    global balance
    for _ in range(counter):
        balance += num
        balance -= num
    if balance != 100:
        print("balance=%d" % balance)
        break

if __name__ == '__main__':
    t1 = threading.Thread(target=change, args=(100, 500000), name='t1')
    t2 = threading.Thread(target=change, args=(100, 500000), name='t2')
    t1.start(); t2.start(); t1.join(); t2.join()
    print("{0} 线程结束".format(threading.current_thread().getName()))</code>

To resolve these issues, a mutex lock is introduced. The lock is created with threading.Lock() , and the critical section (the code that modifies the shared variable) is protected by lock.acquire() and lock.release() . This ensures only one thread can modify the variable at a time, preventing data inconsistency and deadlocks.

<code># 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 关键代码
... 
# 释放
mutex.release()</code>

Finally, the article emphasizes that proper lock usage is essential for thread safety, and that locks should be held only for the minimal necessary duration to avoid performance bottlenecks.

pythonSynchronizationMultithreadingLocksthreadingdaemon
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.