6.1.8 · D4 · HinglishParallelism & Multicore

ExercisesSynchronization primitives (locks, barriers)

2,594 words12 min read↑ Read in English

6.1.8 · D4 · Hardware › Parallelism & Multicore › Synchronization primitives (locks, barriers)

Shuru karne se pehle, ek shared vocabulary refresher (taaki koi bhi symbol use hone se pehle samjha ja sake):

  • = compete karne wale threads ki sankhya.
  • = woh time (seconds mein, ya "instruction-count" mein) jitna ek thread lock hold karta hai — critical section ki length.
  • = ek spin iteration ka time (ek failed test-and-set + loop).
  • Ek spin lock check karta rehta hai; ek blocking lock so jaata hai.
  • Ek condition variable (likha jaata hai cond_t cv, aur abbreviate hota hai CV) ek "waiting room" hai: ek thread is par wait kar sakta hai sone ke liye, jab tak koi doosra thread signal ya broadcast karke use jagaye.

Level 1 — Recognition

L1.1 — Kaun sa primitive?

Tumhare paas do threads hain, dono ek shared counter par counter = counter + 1 ek million baar kar rahe hain. Tum chahte ho ki final value exactly ho. Woh EK primitive batao jo ise fix karta hai, aur bolo ki ek plain bool flag kyun kaam nahi karta.

Recall Solution

Fix hai ek lock (mutex) jo ek hardware atomic jaise Test-and-Set ya Compare-and-Swap par bana hota hai. Ek plain bool flag fail hota hai kyunki "check then set" do instructions hain — check aur set ke beech mein, doosra thread ghus jaata hai. Sirf ek atomic read-modify-write purani value padhta hai AUR nayi value ek indivisible step mein likhta hai, isliye exactly ek hi thread jeet sakta hai. Dekho Atomic operations.

L1.2 — Barrier vs lock

Har scenario ke liye batao ki tumhe lock chahiye ya barrier:

  • (a) Das threads mein se har ek apna partial sum ek shared total mein add karta hai.
  • (b) Ek simulation phases mein chalti hai; har thread ko phase 1 khatam karna hoga isse pehle ki KOI BHI thread phase 2 shuru kare.
Recall Solution
  • (a) Lock — ek shared variable ko simultaneous writes se bachata hai (mutual exclusion).
  • (b) Barrier — ek meeting point jahan sabhi threads wait karte hain jab tak sab aa na jaayein, phir sab milke aage badhte hain. Trick: "ek resource, kaafi writers" → lock. "ek wall jahan sab wait karte hain" → barrier.

Level 2 — Application

L2.1 — Test-and-Set trace karo

TestAndSet(&flag) atomically flag ki OLD value return karta hai aur use 1 set karta hai. Lock free hai ⇒ flag = 0. Teen threads T1, T2, T3 acquire call karte hain aur unke atomic TAS instructions T2, T1, T3 order mein hote hain. Kaun critical section mein jaata hai, aur har TAS kya return karta hai?

Recall Solution

acquire spin karta hai jab tak TAS 1 return karta hai.

  • T2 pehle chalta hai: old value 0 padhta hai, flag ko 1 set karta hai, 0 return karta hai → 0 != 1, toh T2 enter karta hai.
  • T1: old value 1 padhta hai, 1 return karta hai → spin karta hai.
  • T3: old value 1 padhta hai, 1 return karta hai → spin karta hai. Exactly ek winner (T2). Return ki gayi values (T2, T1, T3) order mein [0, 1, 1] hain.

L2.2 — Per-arrival spin wait

Ek lock ke liye hold kiya jaata hai. Ek thread current holder ke critical section ke dauraan ek uniformly random instant par pahunchta hai. Current holder ke release karne ka expected wait kya hai?

Recall Solution


Level 3 — Analysis

L3.1 — FIFO queue average wait

Ab threads almost ek hi waqt par pahunchte hain aur FIFO order mein holder ke peeche queue karte hain. Har critical section leta hai. Per-arrival half-offset ko ignore karte hue, queue position par thread (jahan , kyunki ek lock hold karta hai) apne aage ke sections ke liye wait karta hai. queued threads mein average wait kya hai?

Recall Solution

Dekho Figure s01 neeche: chaar queued threads (bars) wait karte hain, aur dashed line unka mean mark karti hai. Sum . 4 threads mein average: Toh exact answer hai . Parent ka shorthand thoda kam approximation hai — difference pehchaanna achha hai, lekin is discrete FIFO queue ke liye sahi hai.

Figure s01 — FIFO spin-lock queue. Har queued thread ki bar length uska wait () hai; dashed line mean hai.

Figure — Synchronization primitives (locks, barriers)

L3.2 — Spinning se waste hone wali energy

Har spin iteration nJ cost karti hai aur leta hai. Lock ke liye hold kiya jaata hai, aur threads poore time spin karte hain. Use karte hue wasted energy calculate karo.

Recall Solution

Spins per thread iterations. Contention ke saath yeh growth () exactly wahi reason hai kyun blocking locks exist karte hain: ek sota hua thread ~0 energy burn karta hai. Dekho Thread scheduling.


Level 4 — Synthesis

L4.1 — Generation counter kyun?

threads ke liye ek reusable barrier count, threshold=3, aur (crucially) ek generation counter use karta hai, saath mein ek condition variable (CV) — "waiting room" jo upar vocabulary list mein define ki gayi hai. Maan lo last thread doosron ko jagata hai aur count = 0 reset karta hai lekin generation advance NAHI karta. Thread A (fast) jaag jaata hai, loop karke wapas aata hai, aur phase 2 ke liye barrier_wait immediately call karta hai isse pehle ki thread B schedule hoe. Step by step batao kya galat hota hai — aur generation advance karna ise kaise rokta hai.

Recall Solution

Generation ke bina:

  1. Phase 1: A, B, C sab pahunchte hain, count 3 hit karta hai, C count=0 reset karta hai aur CV par broadcast karta hai.
  2. A jaagta hai, niklta hai, phase 2 mein race karta hai, count++ karta hai → count = 1.
  3. B (phase 1 se) jaagne mein slow tha; woh condition re-check karta hai. Lekin condition ("kya barrier khul gayi?") ek fresh phase-2 arrival jaisi lagti hai — B aur A ki states tangled hain. B ya toh bina sab phase-2 ke pahunche slip through kar sakta hai, ya phir permanently count == 3 ke wait mein stuck ho sakta hai jo already reset ho chuka hai. Generation ke saath:
  • Har thread CV par wait karne se pehle my_gen = b->generation record karta hai.
  • Last thread generation++ karta hai, isliye purane aur naye episodes ke different numbers hote hain.
  • Ek jaaga hua thread tabhi aage badhta hai jab b->generation != my_gen — yaani jab uska episode sach mein khatam hua ho, na sirf tab jab CV signal hua ho. Yeh har barrier crossing ko logically alag banata hai, reuse race khatam kar deta hai. Related idea: Memory consistency models mein spurious wakeups ke liye bhi wahi "loop aur real condition re-check" discipline chahiye.

L4.2 — Ordering guarantee

Do threads 0 se initialize flags share karte hain:

Thread 1: x = 1;  r1 = y;
Thread 2: y = 1;  r2 = x;

Ek weak memory model wali machine par, kya hum r1 == 0 && r2 == 0 observe kar sakte hain? Kaun sa primitive/instruction ise forbid karta hai?

Recall Solution

Haan, ek weak model par dono 0 read kar sakte hain: har core apna store apne load ke baad reorder kar sakta hai, isliye dono loads purana 0 dekhte hain isse pehle ki koi store visible ho. Yeh classic "store buffering" outcome hai. Fix hai ek memory fence (ek full barrier instruction) jo har thread mein store aur load ke beech rakha jaata hai. Yeh store ko globally visible hone ke liye force karta hai load execute hone se pehle, r1 == 0 && r2 == 0 ko impossible banata hai. Yeh exactly Memory consistency models ka territory hai aur hood ke andar Cache coherence protocols dwara enforce hota hai.


Level 5 — Mastery

L5.1 — Ek lock + condition variable se counting semaphore banao

Ek semaphore jiska value hai, ek region mein ek saath threads tak allow karta hai (lock special case hai jab ). wait() ko decrement karta hai (block karta hai agar negative ho jaata); signal() ko increment karta hai (ek waiter ko jagata hai). Ek lock aur ek cond variable (CV) use karke correct pseudocode likho, aur mutual-exclusion-style correctness argue karo.

Recall Solution
typedef struct { int S; lock_t lock; cond_t cv; } sem_t;
 
void sem_init(sem_t *s, int val) {
    s->S = val; lock_init(&s->lock); cond_init(&s->cv);
}
 
void sem_wait(sem_t *s) {
    lock(&s->lock);
    while (s->S <= 0)          // LOOP, not if: guards spurious wakeups
        cond_wait(&s->cv, &s->lock);
    s->S--;                    // ab slot lena safe hai
    unlock(&s->lock);
}
 
void sem_signal(sem_t *s) {
    lock(&s->lock);
    s->S++;
    cond_signal(&s->cv);       // ek waiter ko jagao
    unlock(&s->lock);
}

Correctness:

  • lock S par mutual exclusion deta hai, isliye counter par koi lost updates nahi.
  • while (na ki if) jaagne ke baad S > 0 re-check karta hai — spurious wakeups ke against aur doosre thread ke freed slot pehle le lene ke against essential hai.
  • val = 1 set karna ek plain mutex deta hai; bada val multiple threads ko allow karta hai (size ka resource pool).

L5.2 — Lock ordering se deadlock

Thread 1 karta hai acquire(A); acquire(B);. Thread 2 karta hai acquire(B); acquire(A);. Woh interleaving dikhao jo deadlock karta hai, phir woh EK rule do jo SABHI aise deadlocks ko rokta hai.

Recall Solution

Deadlock interleaving:

  1. T1 A acquire karta hai. 2. T2 B acquire karta hai. 3. T1 B ke liye wait karta hai (T2 ke paas hai). 4. T2 A ke liye wait karta hai (T1 ke paas hai). Circular wait → koi aage nahi badhta. Universal fix: ek global lock ordering laago — har thread ko locks same fixed order mein acquire karne chahiye (maan lo, hamesha A pehle, phir B). Phir ek cycle impossible hai: koi bhi thread "higher" lock hold karte hue "lower" lock ka wait nahi karta, isliye Coffman ki chaar conditions mein se ek (circular wait) tod di jaati hai. Dekho Deadlock and livelock.

Recall Quick self-test cloze

Ek plain bool par bana lock fail karta hai kyunki check-and-set ::: atomic nahi hai (do alag steps doosre thread ko interleave karne dete hain) Hold ke liye expected per-arrival spin wait hai ::: Ek barrier ko back-to-back phases mein survive karne ke liye generation counter chahiye ::: reuse ke across (the reuse race) Chaar-condition deadlock ek global ::: lock ordering se tod diya jaata hai (circular wait rokta hai)