Of course! Let's break down the threading.Thread(target=...) constructor in Python, which is the core of creating and managing threads.

The Short Answer
threading.Thread(target=my_function) creates a new thread that, when started, will execute the function my_function. The target argument simply points to the function you want the thread to run.
In-Depth Explanation with Examples
To fully understand target, let's look at the anatomy of a simple threaded program.
The Basic Concept: target and args
When you create a Thread object, you need to tell it two main things:
- What to do: This is the
target. - What to pass to it: This is the
args(a tuple of arguments).
Here's a classic "Hello World" for threading: running two functions concurrently.

import threading
import time
# This is the function that will be run by the thread
def print_numbers(limit):
"""Prints numbers from 1 to a given limit."""
for i in range(1, limit + 1):
print(f"Number: {i}")
time.sleep(0.5) # Simulate a task that takes time
def print_letters(limit):
"""Prints letters from A to a given limit."""
for letter in range(ord('A'), ord('A') + limit):
print(f"Letter: {chr(letter)}")
time.sleep(0.5) # Simulate a task that takes time
# --- Main part of the script ---
if __name__ == "__main__":
# Create the thread objects
# 1. target is the function to run
# 2. args is a tuple of arguments for the target function
thread1 = threading.Thread(target=print_numbers, args=(5,))
thread2 = threading.Thread(target=print_letters, args=(5,))
print("Starting threads...")
# Start the threads (this begins their execution)
thread1.start()
thread2.start()
# Wait for both threads to complete their execution before the main program continues
thread1.join()
thread2.join()
print("All threads finished.")
How it works:
- We define two functions,
print_numbersandprint_letters. - We create two
Threadobjects.- For
thread1,target=print_numberstells it to run theprint_numbersfunction. args=(5,)passes the integer5as an argument toprint_numbers. Note the comma!argsmust be a tuple, so(5,)is a tuple with one element, while(5)is just the number5.
- For
thread1.start()andthread2.start()tell the Python interpreter to begin executing the target functions in new threads. At this point, the main program continues immediately.thread1.join()andthread2.join()are crucial. They pause the main program until each respective thread has finished its work. Without them, the main script might print "All threads finished." before the threads are done.
What if the Function Needs No Arguments?
If your target function doesn't require any arguments, you can simply omit the args parameter.
import threading
import time
def simple_task():
"""A function that takes no arguments."""
print("Starting simple task...")
time.sleep(2)
print("Simple task finished.")
if __name__ == "__main__":
# No args needed
thread = threading.Thread(target=simple_task)
thread.start()
thread.join()
print("Main program finished.")
What if the Function Needs Keyword Arguments?
You can also pass keyword arguments using the kwargs parameter. kwargs must be a dictionary.
import threading
def greet(name, greeting="Hello"):
"""A function that takes positional and keyword arguments."""
print(f"{greeting}, {name}!")
if __name__ == "__main__":
# Pass positional args and keyword args
thread = threading.Thread(
target=greet,
args=("Alice",), # Positional argument for 'name'
kwargs={"greeting": "Hi"} # Keyword argument for 'greeting'
)
thread.start()
thread.join()
# Expected output:
# Hi, Alice!
Key Considerations and Best Practices
The Global Interpreter Lock (GIL)
In CPython (the standard Python implementation), the GIL is a mutex that protects access to Python objects, preventing multiple native threads from executing Python bytecode at the same time within a single process.

- What this means for CPU-bound tasks: For tasks that are heavy on computation (e.g., mathematical calculations, data processing), multithreading in Python does not provide true parallelism. The GIL ensures that only one thread runs at a time, switching between them rapidly.
- What this means for I/O-bound tasks: For tasks that spend most of their time waiting for external operations (e.g., reading/writing files, making network requests), multithreading is highly effective. While one thread is waiting for I/O, the GIL is released, allowing another thread to run.
Conclusion: Use threading for I/O-bound tasks. For CPU-bound tasks, use the multiprocessing module, which creates separate processes and bypasses the GIL.
Sharing Data Between Threads (A Common Pitfall)
Threads share the same memory space. This is powerful but dangerous if not handled correctly, as it can lead to race conditions.
import threading
# A shared variable
shared_counter = 0
def increment_counter():
global shared_counter
for _ in range(1_000_000):
shared_counter += 1 # This is NOT an atomic operation!
if __name__ == "__main__":
threads = []
for _ in range(2):
thread = threading.Thread(target=increment_counter)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"Expected counter: 2,000,000")
print(f"Actual counter: {shared_counter}") # Likely not 2,000,000!
The output will almost certainly be less than 2,000,000. This is because shared_counter += 1 involves three steps: read, modify, write. If two threads interleave these steps, they can overwrite each other's changes.
The Solution: Use Lock to ensure only one thread can modify the shared data at a time.
import threading
shared_counter = 0
# Create a Lock object
counter_lock = threading.Lock()
def increment_counter():
global shared_counter
for _ in range(1_000_000):
with counter_lock: # Acquire the lock before entering the critical section
shared_counter += 1
if __name__ == "__main__":
threads = []
for _ in range(2):
thread = threading.Thread(target=increment_counter)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"Expected counter: 2,000,000")
print(f"Actual counter: {shared_counter}") # Correctly 2,000,000
The with counter_lock: statement ensures that only one thread can execute the code block inside at a time, preventing race conditions.
Summary: target vs. args vs. kwargs
| Argument | Purpose | Type | Example |
|---|---|---|---|
target |
The function to be executed by the thread. | A callable function. | target=my_function |
args |
A tuple of positional arguments to pass to the target function. | A tuple. |
args=(10, "hello") |
kwargs |
A dictionary of keyword arguments to pass to the target function. | A dict. |
kwargs={"name": "Bob", "age": 30} |
