Multithreaded puzzles

I am trying to come up with some multithreading puzzles. Most of the problems I could come up with so far have been quite domain specific. Does anyone have decent programming puzzles for developers trying to learn the basic concepts of multi-threaded applications?

+22
multithreading parallel-processing puzzle
Dec 07 '09 at 20:16
source share
12 answers

There are several topics in this link.

Multithreaded Programming with ThreadMentor: Tutorial

Edit:

Here are some direct links to the problems listed in this link, along with their initial descriptions.

ThreadMentor: Table Philosopher Problem
ThreadMentor: Table Philosopher Problem: Left-Right Version

The problem of table philosophers was invented by E.W. Dijkstra. Imagine that the five philosophers who spend their lives just think east. In the middle of the dining room is a round table with five chairs. The table has a large plate of spaghetti. However, there are only five chopsticks, as shown in the following figure. Every philosopher thinks. When he is hungry, he sits down and picks up two chopsticks that are closest to him. If a philosopher can pick up both chopsticks, he eats for a while. After the philosopher has finished eating, he puts down chopsticks and begins to think.

ThreadMentor: Cigarette Smoking Problem

This problem is associated with S. S. Patil in 1971. Suppose cigarettes require three ingredients: tobacco, paper, and a match. There are three chain smokers. Each of them has only one ingredient with an infinite supply. There is an agent that has an endless supply of all three ingredients. To make a cigarette, the smoker has tobacco (e.g., paper and match), must have two other paper and match ingredients (respectively, tobacco and match, as well as tobacco and paper). Agent and smokers share a table. The agent randomly generates two ingredients and notifies the smoker who needs these two ingredients. When the ingredients are taken from the table, the agent supplies two more. On the other hand, every smoker is waiting for an agent's notice. As soon as he is notified, the smoker will pick up the ingredients, write down a cigarette, smoke for a while and return to the table, waiting for his next ingredients.

ThreadMentor: producer / consumer problem (or limited buffer)

Suppose we have a circular buffer with two pointers in and out to indicate the next available data entry position and the position that contains the next data to be received. See the chart below. There are two groups of threads, producers and consumers. Each producer puts data elements in position and advances the pointer, and each consumer retrieves the data element in position and advances the pointer.

ThreadMentor: Roller Conveyor Problem

Suppose there are n passengers and one rollercoaster car. Passengers wait many times to travel in a car where passengers can be at maximum C, where C <n. However, a car can only move along the highway when it is full. After completing the trip, each passenger wanders around the amusement park before returning to the roller coaster for another trip. Due to safety reasons, the car only drives T times and then fired.

This has additional restrictions:

  • The car always drives smoothly with passengers C;
  • No passengers will jump from the car while the car is running;
  • No passengers will jump on the car while the car is running;
  • No passengers will be asked to drive before they can get out of the car.

ThreadMentor: Bridge Problem

The description for this depends on the images. The following is a modified quote with the removal of links to images.

Consider a narrow bridge that allows you to simultaneously allow the simultaneous intersection of three vehicles in one direction. If there are three cars on the bridge, any incoming vehicle must wait until the bridge becomes clean.

When a vehicle exits a bridge, we must consider two cases. Case 1, there are other vehicles on the bridge; and case 2 is the last car on the last bridge. In the first case, it is necessary to continue the movement of one new vehicle in the same direction.

Case 2 is more complicated and has two subcases. In this case, the exit vehicle is the last vehicle on the bridge. If there are vehicles waiting in the opposite direction, one of them should be allowed. Or, if there is no vehicle waiting in the opposite direction, then let the waiting vehicle move in the same direction.

+11
Dec 07 '09 at 20:26
source share

"The Dining Problems of Philosophers" is the first that I think of.

+6
Dec 07 '09 at 20:21
source share

You have a large tree structure in mind. Many threads need to look for structure. Sometimes a thread must insert or remove something from a structure. How do you control access to the structure so that the program works correctly (no thread will stomp on each other when changing the structure) and efficiently (no threads are blocked when they should not be)?

+2
Dec 07 '09 at 20:25
source share
+2
Dec 07 '09 at 20:25
source share

Dining philosophers are one of the ...

unisex bathroom is another

+1
Dec 07 '09 at 20:23
source share

Perhaps you can use the simple problem of testing and setting a common flag or accessing some list resource in some sequential, sequential way?

+1
Dec 07 '09 at 20:25
source share
+1
Dec 07 '09 at 20:26
source share

Here is the first problem I have ever ended up with multithreaded, even during my student studies.

+1
Dec 07 '09 at 20:32
source share

Depending on what you do with multithreading, it matters.

You are in the bank. Customers receive an average bid once every 2 minutes. Each client is served on average in 2 minutes.

What is the best customer service solution? One common line or one line for each counter?

Is your choice sufficient to guarantee a certain boundary along the length of the line?

Answers: due to the Markov property of the client’s arrival and the actual service time for each person, the line will never know about the restriction. In addition, it is a good idea to make them wait in one common line, although this is not enough to overcome the limitless line.

+1
Dec 07 '09 at 20:45
source share

Linear simulator is quite common.

+1
Dec 07 '09 at 20:47
source share

Here's a parallel N-puzzle implemented in PARLANSE . The language has LISP syntax, but is really closer to C (scalars, structures, pointers, function calls), but unlike C, it has local areas. The secret lies in the parallel fork-grain operator (|| ...), which executes all its operands in parallel, as well as the ability of PARLANSE to use exceptions to stop the parent grains.

This solver provides linear accelerations on all 4 and 8 position machines I tried it on.

(define Version `N-puzzle Solver V1.1~l Copyright (C) 1998-2009 Semantic Designs; All Rights Reserved~l') (define SolveParticularPuzzle ~t) (define ManhattanHeuristic ~t) ; Manhattan is really fast (define PrintTrace ~f) (include `parmodule.par') (define ScrambleCount 10000) (define PuzzleSize `Length of side of N-puzzle' +4) ; at least 3! (define PuzzleSizeMinus1 +3) (define PuzzleArea `Area of puzzle (= (-- N))' +16) ; (= (* PuzzleSize PuzzleSize)) (define PuzzleAreaMinus1 +15) (define BlankTile `Code for a blank tile' 0) (define puzzlepieceT `Codes for nonblank tiles' (sort natural (range 1 PuzzleArea))) (define BoardPositionT integer) ; normally positive, but sometime we reach off the edge (define ConfigurationT (array puzzlepieceT 0 PuzzleAreaMinus1)) (define HardPuzzle1 `Solution found of length 29: 2 1 5 6 2 3 7 11 10 6 2 3 7 11 10 14 13 9 8 12 13 9 5 1 2 6 5 1 0' (lambda (function ConfigurationT void) (make ConfigurationT 01 11 02 00 04 06 09 05 13 12 07 03 08 14 10 15) )lambda )define (define HardPuzzle2 `Solution found of length 31: 0 4 5 6 10 9 5 1 2 3 7 6 10 9 5 1 2 3 7 6 5 1 2 6 1 0 14 13 9 5 4 0' (lambda (function ConfigurationT void) (make ConfigurationT 13 00 02 09 04 05 06 01 08 07 03 11 12 14 10 15) )lambda )define (define HardPuzzle3 `Solution found of length 56: 1 2 6 7 3 2 6 10 14 15 11 10 9 5 4 8 12 13 9 10 6 5 1 0 4 8 12 13 14 10 6 7 11 10 9 13 14 15 11 10 6 5 4 8 9 10 6 5 1 0 4 8 9 5 4 0 Total solution time in seconds: 18-24 (on 8 processor machine)' (lambda (function ConfigurationT void) (make ConfigurationT 00 09 10 08 15 12 03 02 01 11 13 14 06 04 07 05) )lambda )define (define HardPuzzle4 `Solution found of length 50: 4 5 1 0 4 8 12 13 9 5 1 0 4 5 6 10 14 13 9 8 4 5 6 2 1 5 9 10 14 13 12 8 9 10 11 15 14 13 9 10 11 7 3 2 1 5 9 8 4 0 Total solution time in seconds: 125 (on 8 processor machine)' (lambda (function ConfigurationT void) (make ConfigurationT 00 15 06 07 12 03 08 11 04 13 02 05 01 14 09 10) )lambda )define (define HardPuzzle5 `Solution found of length 68: 3 7 11 10 6 2 3 7 6 5 9 8 4 5 1 0 4 5 9 13 14 15 11 7 6 5 1 2 6 5 9 8 12 13 14 10 6 5 4 8 12 13 14 15 11 10 9 5 1 0 4 8 12 13 9 5 4 8 9 13 14 15 11 7 3 2 1 0 Total solution time in seconds: 2790 (on 8 processor machine)' (lambda (function ConfigurationT void) (make ConfigurationT 15 09 00 14 10 11 12 08 03 02 13 07 01 06 05 04) )lambda )define (define ParticularPuzzleToSolve HardPuzzle5) (define PrintConfiguration (action (procedure [Puzzle (reference ConfigurationT)]) (do [position BoardPositionT] +0 PuzzleAreaMinus1 +1 (;; (ifthenelse (<= Puzzle:position 9) (;; (PAR:PutConsoleCharacter "0")(PAR:PutConsoleNatural Puzzle:position) );; (PAR:PutConsoleNatural Puzzle:position) )ifthenelse (PAR:PutConsoleSpace) (ifthen (== (modulo (coerce natural position) (coerce natural PuzzleSize)) (coerce natural PuzzleSizeMinus1)coerce )== (PAR:PutConsoleNewline) )ifthen );; )do )action )define (define Solved? `Determines if puzzle is solved.' (lambda (function boolean [board (reference ConfigurationT)] )function (value (;; `Fast check for completed': (ifthen (~= board:0 BlankTile) (return ~f) )ifthen (do [position BoardPositionT] PuzzleAreaMinus1 +1 -1 (ifthen (~= board:position (coerce natural position)) (return ~f) )ifthen )do );; ~t ; all pieces are in proper places )value )lambda )define (define ScoreT `Estimate of configuration distance from solution. Zero means configuration is a solution.' (sort natural (range 0 1000))) ; s/b (range 0 (* PuzzleArea PuzzleArea)) (define SolvedScore `The score of a goal position.' 0) (define UnsolvableScore `An impossibly big score.' 12345678) (define LowerBoundOnScore (lambda (function ScoreT [Puzzle (reference ConfigurationT)]) (let (= [OutOfPlaceTiles ScoreT] 0) (value (compileifthenelse ManhattanHeuristic ; ~t for Out-of-place, ~f for Manhattan (do [Row BoardPositionT] PuzzleSizeMinus1 +0 -1 (do [Column BoardPositionT] PuzzleSizeMinus1 +0 -1 (local (;; (= [position integer] (+ (* Row PuzzleSize) Column))= (= [tile puzzlepieceT] Puzzle:position) );; (ifthen (~= tile BlankTile) ; ignore BlankTile (+= OutOfPlaceTiles (+ (magnitude (- Row (coerce integer (// tile (coerce natural PuzzleSize))))) (magnitude (- Column (coerce integer (modulo tile (coerce natural PuzzleSize))))) )+ ; add Manhattan distance of tile from tile goal )+= )ifthen )local )do ; Column )do ; Row (do [position BoardPositionT] PuzzleAreaMinus1 +1 ; skipping zero effectively ignores BlankTile +1 (ifthen (~= Puzzle:position (coerce natural position)) (+= OutOfPlaceTiles) )ifthen )do )compileifthenelse OutOfPlaceTiles ; the answer )value )let )lambda )define (recursive PathElementT (define PathElementT `A series of moves of the blank tile.' (structure [Move BoardPositionT] [Next (reference PathElementT)] )structure )define )recursive (define EmptyPath (void (reference PathElementT))void )define (define ValuedPathT `A path and the score it acheives.' (structure [Solved boolean] [Score ScoreT] [Path (reference PathElementT)]) )define (define MakeMove `Applies a move to a configuration' (lambda (function ConfigurationT (structure [BlankTilePosition BoardPositionT] [NewBlankPosition BoardPositionT] [ConfigurationBeforeMove (reference ConfigurationT)] )structure )function (let (= [ResultConfiguration ConfigurationT] (@ ConfigurationBeforeMove) )= (value (;; (compileifthen PrintTrace (;; (PAR:PutConsoleNatural BlankTilePosition) (PAR:PutConsoleNatural NewBlankPosition) );; )compileifthen (trust (== ConfigurationBeforeMove:BlankTilePosition BlankTile)) (= ResultConfiguration:BlankTilePosition ConfigurationBeforeMove:NewBlankPosition) (= ResultConfiguration:NewBlankPosition BlankTile) );; ResultConfiguration )value )let )lambda )define (define TopEdge? `Determines if a position is along top edge of puzzle.' (lambda (function boolean BoardPositionT) (< ? PuzzleSize) )lambda )define (define BottomEdge? `Determines if a position is along bottom edge of puzzle.' (lambda (function boolean BoardPositionT) (>= ? (- PuzzleArea PuzzleSize)) )lambda )define (define LeftEdge? `Determines if a position is along left edge of puzzle.' (lambda (function boolean BoardPositionT) (== (modulo (coerce natural ?) (coerce natural PuzzleSize)) 0)== )lambda )define (define RightEdge? `Determines if a position is along right edge of puzzle.' (lambda (function boolean BoardPositionT) (== (modulo (coerce natural ?) (coerce natural PuzzleSize))modulo (coerce natural PuzzleSizeMinus1)coerce )== )lambda )define (define Solved! (exception (lambda (function string (reference ValuedPathT)) `N-puzzle solution is:~l' )lambda )exception )define [SerialPrint semaphore] [MaxMoves natural] (define Npuzzle (lambda (function ValuedPathT [BlankTilePosition BoardPositionT] [PreviousBlankTilePosition BoardPositionT] [Puzzle ConfigurationT] [MovesToHere natural] )function )lambda )define (define Npuzzle `Solves a puzzle and generates a sequence which is a solution.' (lambda (function ValuedPathT [BlankTilePosition BoardPositionT] [PreviousBlankTilePosition BoardPositionT] [Puzzle ConfigurationT] [MovesToHere natural] )function (ifthenelse (value (compileifthen PrintTrace (;; (PAR:PutConsole (. `In Npuzzle at depth ')) (PAR:PutConsoleNatural MovesToHere) (PAR:PutConsoleNewline) (PrintConfiguration (. Puzzle)) );; )compileifthen (Solved? (. Puzzle))) (make ValuedPathT ~t 0 EmptyPath)make ; the answer (let (|| [valuedpath1 ValuedPathT] [valuedpath2 ValuedPathT] [valuedpath3 ValuedPathT] [valuedpath4 ValuedPathT] [Best ValuedPathT] (= [EstimatedDistance natural] (+ MovesToHere (LowerBoundOnScore (. Puzzle)))+ )= )|| (ifthenelse (value (compileifthen PrintTrace (;; (PAR:PutConsole (. `Inside LET EstimatedDistance= ')) (PAR:PutConsoleNatural EstimatedDistance) (PAR:PutConsoleNewline) );; )compileifthen (> EstimatedDistance MaxMoves) ) (make ValuedPathT ~f EstimatedDistance EmptyPath) ; don't explore any further (value (;; (assert (& (<= +0 BlankTilePosition) (< BlankTilePosition PuzzleArea) )& )assert ; (PAR:PutConsole (. `Solve subpuzzles: blank @ '))(PAR:PutConsoleNatural BlankTilePosition)(PAR:PutConsoleNewline) (try `Solve subpuzzles': (|| ; replace this by (;; to see pure serial execution times `Fork Right': (local (|| (= [NewBlankTilePosition BoardPositionT] (++ BlankTilePosition) )= [ExtendedPath (reference PathElementT)] )|| (ifthenelse (value (;; ; (PAR:PutConsole (. `Fork Right~l')) );; (&& (~= NewBlankTilePosition PreviousBlankTilePosition )~= (~ (RightEdge? BlankTilePosition))~ )&& )value (;; (= valuedpath1 (Npuzzle NewBlankTilePosition BlankTilePosition (MakeMove BlankTilePosition NewBlankTilePosition (. Puzzle) )MakeMove (++ MovesToHere) )Npuzzle )= (ifthen valuedpath1:Solved (;; (+= valuedpath1:Score) ; since we added a move (= ExtendedPath (new PathElementT)) (= (@ ExtendedPath) (make PathElementT NewBlankTilePosition valuedpath1:Path) )= (= valuedpath1:Path ExtendedPath) (raise Solved! (. valuedpath1)) );; )ifthen );; (= valuedpath1 (make ValuedPathT ~f UnsolvableScore EmptyPath))= )ifthenelse )local `Fork Left': (local (|| (= [NewBlankTilePosition BoardPositionT] (-- BlankTilePosition) )= [ExtendedPath (reference PathElementT)] )|| (ifthenelse (value (;; ; (PAR:PutConsole (. `Fork Left~l')) );; (&& (~= NewBlankTilePosition PreviousBlankTilePosition )~= (~ (LeftEdge? BlankTilePosition))~ )&& )value (;; (= valuedpath2 (Npuzzle NewBlankTilePosition BlankTilePosition (MakeMove BlankTilePosition NewBlankTilePosition (. Puzzle) )MakeMove (++ MovesToHere) )Npuzzle )= (ifthen valuedpath2:Solved (;; (+= valuedpath2:Score) ; since we added a move (= ExtendedPath (new PathElementT)) (= (@ ExtendedPath) (make PathElementT NewBlankTilePosition valuedpath2:Path) )= (= valuedpath2:Path ExtendedPath) (raise Solved! (. valuedpath2)) );; )ifthen );; (= valuedpath2 (make ValuedPathT ~f UnsolvableScore EmptyPath))= )ifthenelse )local `Fork Down': (local (|| (= [NewBlankTilePosition BoardPositionT] (- BlankTilePosition PuzzleSize) )= [ExtendedPath (reference PathElementT)] )|| (ifthenelse (value (;; ; (PAR:PutConsole (. `Fork Down~l')) );; (&& (~= NewBlankTilePosition PreviousBlankTilePosition )~= (~ (TopEdge? BlankTilePosition))~ )&& )value (;; (= valuedpath3 (Npuzzle NewBlankTilePosition BlankTilePosition (MakeMove BlankTilePosition NewBlankTilePosition (. Puzzle) )MakeMove (++ MovesToHere) )Npuzzle )= (ifthen valuedpath3:Solved (;; (+= valuedpath3:Score) ; since we added a move (= ExtendedPath (new PathElementT)) (= (@ ExtendedPath) (make PathElementT NewBlankTilePosition valuedpath3:Path) )= (= valuedpath3:Path ExtendedPath) (raise Solved! (. valuedpath3)) );; )ifthen );; (= valuedpath3 (make ValuedPathT ~f UnsolvableScore EmptyPath))= )ifthenelse )local `Fork Up': (local (|| (= [NewBlankTilePosition BoardPositionT] (+ BlankTilePosition PuzzleSize) )= [ExtendedPath (reference PathElementT)] )|| (ifthenelse (value (;; ; (PAR:PutConsole (. `Fork Up~l')) );; (&& (~= NewBlankTilePosition PreviousBlankTilePosition )~= (~ (BottomEdge? BlankTilePosition))~ )&& )value (;; (= valuedpath4 (Npuzzle NewBlankTilePosition BlankTilePosition (MakeMove BlankTilePosition NewBlankTilePosition (. Puzzle) )MakeMove (++ MovesToHere) )Npuzzle )= (ifthen valuedpath4:Solved (;; (+= valuedpath4:Score) ; since we added a move (= ExtendedPath (new PathElementT)) (= (@ ExtendedPath) (make PathElementT NewBlankTilePosition valuedpath4:Path) )= (= valuedpath4:Path ExtendedPath) (raise Solved! (. valuedpath4)) );; )ifthen );; (= valuedpath4 (make ValuedPathT ~f UnsolvableScore EmptyPath))= )ifthenelse )local ) ; || or ;; `Exception handler': (;; ; (PAR:PutConsole (. `Exception handler~l')) (ifthenelse (== (exception) Solved!)== (;; (= Best (@ (exceptionargument (reference ValuedPathT))))= (acknowledge (;; );; )acknowledge );; (propagate) ; oops, something unexpected! )ifthenelse );; `Success handler': (;; ; (PAR:PutConsole (. `Success (no exception raised)!~l')) `If we get here, no result is a solution, and all results have leaf-estimated scores.' (ifthenelse (< valuedpath1:Score valuedpath2:Score) (= Best valuedpath1) (= Best valuedpath2) )ifthenelse (ifthen (< valuedpath3:Score Best:Score) (= Best valuedpath3) )ifthen (ifthen (< valuedpath4:Score Best:Score) (= Best valuedpath4) )ifthen );; )try );; Best ; the answer to return )value )ifthenelse )let )ifthenelse )lambda )define [StartTimeMicroseconds natural] (define ElapsedTimeSeconds `Returns time in seconds rounded to nearest integer' (lambda (function natural void) (/ (- (+ (MicrosecondClock) 500000) StartTimeMicroseconds) 1000000) )lambda )define (define main (action (procedure void) (local (|| [PuzzleToSolve ConfigurationT] [BlankTilePosition BoardPositionT] [Solution ValuedPathT] [BlankLocation BoardPositionT] [Neighbor BoardPositionT] [PathScanP (reference PathElementT)] [ElapsedTime natural] )|| (;; (PAR:PutConsoleString Version) (consume (addresource SerialPrint 1)) `Set PuzzleToSolve to Solved position': (do [position BoardPositionT] +0 PuzzleAreaMinus1 +1 (= PuzzleToSolve:position (coerce puzzlepieceT position) )= )do (ifthenelse SolveParticularPuzzle (;; (PAR:PutConsole (. `Hard puzzle...~l')) (= PuzzleToSolve (ParticularPuzzleToSolve) )= );; (;; `Scramble puzzle position' (PAR:PutConsole (. `Random puzzle...~l')) (= BlankLocation +0) (do [i natural] 1 (modulo (MicrosecondClock) ScrambleCount)modulo 1 (;; (= Neighbor BlankLocation) (ifthenelse (== (PAR:GetRandomNat 2) 0) (;; `Move Blank up or down' (ifthenelse (== (PAR:GetRandomNat 2) 0) (ifthen (~ (TopEdge? BlankLocation)) (-= Neighbor PuzzleSize)) (ifthen (~ (BottomEdge? BlankLocation)) (+= Neighbor PuzzleSize)) )ifthenelse );; (;; `Move Blank left or right' (ifthenelse (== (PAR:GetRandomNat 2) 0) (ifthen (~ (LeftEdge? BlankLocation)) (-= Neighbor)) (ifthen (~ (RightEdge? BlankLocation)) (+= Neighbor)) )ifthenelse );; )ifthenelse ; (PAR:PutConsoleNatural BlankLocation)(PAR:PutConsoleNatural Neighbor)(PAR:PutConsoleSpace) (ifthen (~= BlankLocation Neighbor) (= PuzzleToSolve (MakeMove BlankLocation Neighbor (. PuzzleToSolve). )MakeMove )= )ifthen (= BlankLocation Neighbor)= );; )do );; )ifthenelse (;; `Initialize solver' (= Solution:Solved ~f) (= Solution:Score 0) (do FindBlankTile [position BoardPositionT] +0 PuzzleAreaMinus1 +1 (ifthen (== PuzzleToSolve:position BlankTile) (;; (= BlankTilePosition position) (exitblock FindBlankTile) );; )ifthen )do );; (PAR:PutConsole (. `~lInitial Configuration:~l')) (PrintConfiguration (. PuzzleToSolve)) (PAR:PutConsole (. `Estimate of solution length: ')) (PAR:PutConsoleNatural (LowerBoundOnScore (. PuzzleToSolve))) (PAR:PutConsoleNewline) (= StartTimeMicroseconds (MicrosecondClock)) (while (~ Solution:Solved) (;; (critical SerialPrint 1 (;; (PAR:PutConsole (. `*** Iteration to depth ')) (PAR:PutConsoleNatural Solution:Score) (PAR:PutConsole (. ` ')) (PAR:PutConsoleNatural (ElapsedTimeSeconds)) (PAR:PutConsole (. ` Seconds')) (PAR:PutConsoleNewline) );; )critical (= MaxMoves Solution:Score) (= Solution (Npuzzle BlankTilePosition BlankTilePosition PuzzleToSolve 0) )= );; )while (= ElapsedTime (ElapsedTimeSeconds)) (critical SerialPrint 1 (;; (PAR:PutConsole (. `Solution found of length ')) (PAR:PutConsoleNatural Solution:Score) (PAR:PutConsole (. `: ')) (iterate (= PathScanP Solution:Path) (~= PathScanP EmptyPath) (= PathScanP PathScanP:Next) (;; (PAR:PutConsoleNatural (coerce natural PathScanP:Move)) (PAR:PutConsoleSpace) );; )iterate (PAR:PutConsoleNewline) (PAR:PutConsole (. `Total solution time in seconds: ')) (PAR:PutConsoleNatural ElapsedTime) (PAR:PutConsoleNewline) );; )critical );; )local )action )define 
+1
Dec 08 '09 at 10:45
source share

The small semaphore book , in which the book is freely available, contains many synchronization puzzles. It includes almost all the puzzles given in the other answers. Solutions are provided for all puzzles.

+1
Feb 16 2018-12-16T00
source share



All Articles