threading
programming
multithreading
concurrency
argument-passing

How to pass arguments to a thread?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Passing arguments to threads is fundamental for concurrent programming. Every major language has its own mechanism: Python uses the args and kwargs parameters of threading.Thread, Java passes data through constructors or lambda captures, C# uses ParameterizedThreadStart or lambdas, C++ uses std::thread constructor arguments, and Go uses goroutine function parameters. The key concern across all languages is thread safety — shared mutable data must be protected to avoid race conditions.

Python

python
1import threading
2
3def worker(name, count, verbose=False):
4    for i in range(count):
5        if verbose:
6            print(f"[{name}] iteration {i}")
7
8# Positional arguments via args tuple
9t1 = threading.Thread(target=worker, args=("Thread-1", 5))
10
11# Keyword arguments via kwargs dict
12t2 = threading.Thread(target=worker, args=("Thread-2", 3), kwargs={"verbose": True})
13
14t1.start()
15t2.start()
16t1.join()
17t2.join()

args must be a tuple. A single argument requires a trailing comma: args=(value,). Forgetting the comma passes the value as a non-tuple iterable.

Python with a Shared Mutable Argument

python
1import threading
2
3results = []
4lock = threading.Lock()
5
6def compute(n, output):
7    result = n * n
8    with lock:
9        output.append(result)
10
11threads = []
12for i in range(5):
13    t = threading.Thread(target=compute, args=(i, results))
14    threads.append(t)
15    t.start()
16
17for t in threads:
18    t.join()
19
20print(sorted(results))  # [0, 1, 4, 9, 16]

When threads share a mutable argument (like a list), protect writes with a Lock to prevent race conditions.

Java

java
1// Using Runnable with constructor
2public class Worker implements Runnable {
3    private final String name;
4    private final int iterations;
5
6    public Worker(String name, int iterations) {
7        this.name = name;
8        this.iterations = iterations;
9    }
10
11    @Override
12    public void run() {
13        for (int i = 0; i < iterations; i++) {
14            System.out.println(name + " iteration " + i);
15        }
16    }
17}
18
19// Usage
20Thread t = new Thread(new Worker("Worker-1", 5));
21t.start();
22
23// Using lambda (Java 8+) — captures variables
24String taskName = "Lambda-Worker";
25int count = 3;
26Thread t2 = new Thread(() -> {
27    for (int i = 0; i < count; i++) {
28        System.out.println(taskName + " iteration " + i);
29    }
30});
31t2.start();

Lambda-captured variables must be effectively final in Java. If you need to modify them, use AtomicInteger or an array wrapper.

C#

csharp
1// Using lambda (preferred)
2string name = "Worker-1";
3int count = 5;
4var thread = new Thread(() =>
5{
6    for (int i = 0; i < count; i++)
7        Console.WriteLine($"{name} iteration {i}");
8});
9thread.Start();
10
11// Using ParameterizedThreadStart
12var thread2 = new Thread(obj =>
13{
14    var args = ((string Name, int Count))obj!;
15    for (int i = 0; i < args.Count; i++)
16        Console.WriteLine($"{args.Name} iteration {i}");
17});
18thread2.Start(("Worker-2", 3));
19
20// Using Task (modern approach)
21await Task.Run(() =>
22{
23    // Captured variables from outer scope
24    Console.WriteLine($"Task running for {name}");
25});

Lambdas capture variables by reference in C#. If the variable changes before the thread runs, the thread sees the new value. Copy to a local variable for safety.

C++

cpp
1#include <thread>
2#include <iostream>
3#include <string>
4
5void worker(std::string name, int count) {
6    for (int i = 0; i < count; ++i) {
7        std::cout << name << " iteration " << i << std::endl;
8    }
9}
10
11int main() {
12    // Arguments are copied by default
13    std::thread t1(worker, "Thread-1", 5);
14
15    // Pass by reference using std::ref
16    int shared_counter = 0;
17    std::thread t2([&shared_counter]() {
18        shared_counter++;  // WARNING: not thread-safe without mutex
19    });
20
21    t1.join();
22    t2.join();
23
24    return 0;
25}

std::thread copies arguments by default. Use std::ref() to pass by reference, and protect shared data with std::mutex.

Go

go
1package main
2
3import (
4    "fmt"
5    "sync"
6)
7
8func worker(name string, count int, wg *sync.WaitGroup) {
9    defer wg.Done()
10    for i := 0; i < count; i++ {
11        fmt.Printf("%s iteration %d\n", name, i)
12    }
13}
14
15func main() {
16    var wg sync.WaitGroup
17
18    wg.Add(2)
19    go worker("Goroutine-1", 5, &wg)
20    go worker("Goroutine-2", 3, &wg)
21
22    wg.Wait()
23}

Goroutines take function arguments directly. Arguments are evaluated at the go statement, so they are safe from the loop variable capture problem as long as you pass them as parameters.

Go: Loop Variable Capture

go
1// BUG: all goroutines see the final value of i
2for i := 0; i < 5; i++ {
3    go func() {
4        fmt.Println(i)  // Prints 5 five times
5    }()
6}
7
8// Fix: pass i as a parameter
9for i := 0; i < 5; i++ {
10    go func(n int) {
11        fmt.Println(n)  // Prints 0, 1, 2, 3, 4 (in any order)
12    }(i)
13}

Common Pitfalls

  • Python single-arg tuple: args=(value) is not a tuple — it is just value in parentheses. Use args=(value,) with a trailing comma for a single argument.
  • Java lambda variable must be final: Lambdas in Java can only capture effectively final variables. If you need mutable state, use AtomicReference, AtomicInteger, or pass an object.
  • C# closure over loop variable: for (int i = 0; i < 5; i++) new Thread(() => Console.Write(i)).Start() may print 5 five times. Copy i to a local: var local = i;
  • C++ dangling reference: Passing a local variable by std::ref to a detached thread causes undefined behavior if the variable goes out of scope before the thread finishes. Either join the thread or copy the data.
  • Go loop variable capture: Goroutines with closures capture the loop variable by reference. Pass it as a function argument to get a per-iteration copy.

Summary

  • Python: threading.Thread(target=fn, args=(a, b), kwargs={"key": val})
  • Java: pass via constructor (Runnable) or lambda capture (effectively final only)
  • C#: lambda capture or ParameterizedThreadStart, prefer Task.Run for modern code
  • C++: std::thread(fn, arg1, arg2) copies by default, use std::ref() for references
  • Go: pass arguments directly to the goroutine function to avoid closure capture bugs
  • Always protect shared mutable data with locks, mutexes, or atomic types

Course illustration
Course illustration

All Rights Reserved.