Kahibaro
Discord Login Register

Synchronization mechanisms

Why Synchronization Is Needed in Shared-Memory Programs

In shared-memory parallel programs, multiple threads can read and write the same variables in memory. Without coordination, they may:

Synchronization mechanisms provide controlled ways for threads to:

This chapter focuses on the main synchronization tools you’ll use in typical OpenMP-style shared-memory programming, without going into general OpenMP syntax that belongs elsewhere.

Kinds of Synchronization

At a high level, synchronization mechanisms fall into a few categories:

Each has different performance characteristics and appropriate use-cases.

Mutual Exclusion: Locks and Critical Sections

Critical Sections

A critical section is a block of code that must be executed by at most one thread at a time. Conceptually:

// Pseudocode
critical {
    // Access or update shared variable(s)
}

Typical use-cases:

Critical sections are easy to use but can become performance bottlenecks if:

The key design principle is to minimize the amount of work done inside critical sections.

Locks (Mutexes)

A lock (often called a mutex) is a lower-level primitive for mutual exclusion. The idea:

Pseudocode example:

lock_init(L);
parallel {
    // Some thread-private work...
    lock_acquire(L);
    // Critical region: safely update shared data
    shared_sum += local_sum;
    lock_release(L);
}
lock_destroy(L);

Locks give you more flexibility than high-level critical constructs:

But they also introduce more risk:

Design tips:

Barriers

A barrier forces all threads in a parallel region to wait until every thread has reached that barrier. After the barrier:

Conceptual pseudocode:

parallel {
    // Phase 1: compute partial results
    compute_partial_result();
    barrier();  // Wait for all threads
    // Phase 2: use results from all threads
    use_all_partial_results();
}

Common uses:

Overuse of barriers can degrade performance:

Good practice:

Atomic Operations

Sometimes you only need to protect a very small, simple update to a shared variable, such as:

Using a full lock for these can be overkill; atomic operations perform such updates as an indivisible unit, often with hardware support.

Conceptual example:

parallel {
    // Each thread has some local contribution
    atomic_add(global_sum, local_sum);  // implemented atomically
}

Characteristics:

Limitations:

Rule of thumb:

Condition Synchronization and Signaling

Beyond mutual exclusion and barriers, you often need mechanisms where:

This is known as condition synchronization or signaling.

Common patterns:

Conceptual pseudocode with condition variables:

// Shared:
queue Q;
lock L;
condition not_empty;
producer_thread() {
    while (more_work) {
        item = produce_item();
        lock_acquire(L);
        enqueue(Q, item);
        signal(not_empty);    // Wake a waiting consumer
        lock_release(L);
    }
}
consumer_thread() {
    while (true) {
        lock_acquire(L);
        while (is_empty(Q)) {
            wait(not_empty, L);  // Atomically release L and block
        }
        item = dequeue(Q);
        lock_release(L);
        consume_item(item);
    }
}

Key properties:

These mechanisms are more advanced but essential for complex shared-memory applications that need event-driven or asynchronous coordination, rather than simple phase-based barriers.

Memory Visibility and Ordering

Synchronization does not only control “who runs what when”; it also affects how and when memory updates become visible to other threads.

Modern CPUs and compilers can:

Synchronization primitives have memory ordering semantics:

These operations typically act as memory fences:

Practical implications:

Choosing the Right Mechanism

Selecting the right synchronization tool is crucial for both correctness and performance:

General performance guidelines:

Common Pitfalls Related to Synchronization

While details of race conditions are covered elsewhere, synchronization mechanisms are closely tied to several typical mistakes:

Good habits:

Summary

Synchronization mechanisms are the backbone of safe shared-memory parallel programming. They allow threads to:

Effective use of synchronization is about balancing correctness with performance: enough synchronization to avoid bugs, but no more than necessary to maintain scalability.

Views: 11

Comments

Please login to add a comment.

Don't have an account? Register now!