190 likes | 204 Views
Explore the implementation of a two-lock concurrent queue, modeling a simulation, identifying shared data, synchronization primitives, testing, and evaluating performance in Fall 2007. Understand the requirements, operations, and challenges faced during the implementation process.
E N D
Implementing a Two-LockConcurrent Queue Hussain Tinwala Fall 2007
Overview • Description & Requirements • Implementing the Queue • Modeling a Simulation • Identifying Shared Data • Using Synchronization Primitives • Testing and Performance
Queue Description & Requirements (1) • Concurrent Queue (FIFO) • Concurrent Operations • Enqueue: add a new node to the back. • Dequeue: remove a node from the front. • Queue Access Points • Head Node • Tail Node • Internal Node Structure is a linked list
Queue Description & Requirements (2) • Single Lock Queue All operations proceed one after another • Two Lock Queue Some operations can proceed at the same time
Implementing the Queue (1) • Initial Queue Head Tail next dummy null
Implementing the Queue (2) • Enqueue works with the tailLock Assume newNode is already created Part of the Enqueue Method
Implementing the Queue (3) • Dequeue works with the headLock Part of the Dequeue Method
Modeling a Simulation (2) • Enqueuer class and Dequeuer class share a Producer-Consumer Relationship • Enqueuer threads: • Start • Enqueue n times • Exit • Dequeuer threads: • Start • Dequeue until queue is empty. • Once the queue is empty, the Dequeuer thread will • Wait until new data arrives…. OR • Exit if no more data will arrive
Modeling a Simulation (3) • Simulator Responsibilities: • Construct and Initialize the Queue • Spawn Enqueuer and Dequeuer threads with appropriate arguments • Number of nodes per Enqueue • Time to sleep • Provide a global repository for storing non-queue related shared data • Number of Enqueuer threads left (Why? --> Next slide)
Identifying Shared Data • Instances of the following classes are shared between Enqueuers and Dequeuers: • Queue (to perform Enqueue/Dequeue operations) • Simulator • The Role of the Simulator class in the Producer-Consumer relationship • Used to inform Dequeuer (consumer) threads of: • Arrival of new Data • The end of production
Using Synchronization Primitives (1) • The Dequeuer thread keeps running until there is no more data left in the queue. • Example: 2 Enqueuer threads (each enqueues 2 nodes, so 4 nodes in all), 1 Dequeuer threadNotation: [ThreadID, NodeNumber] [1,1] [2,1] • A Dequeuer thread now Dequeues twice [1,1] [2,1] The queue is now defined to be empty • The Dequeuer thread goes to sleep and Enqueuer threads continue [2,1] [2,2] [1,2] • The Enqueuer thread uses the Simulator to wake up Dequeuerand Enqueuer threads exit. [2,1] [2,2] [1,2] • The simulator announces that no more data will arrive. The Dequeuer • sees this by inspecting an integer in the shared Simulator object and exits.
Using Synchronization Primitives (2) Synchronization actions used by the Enqueuer class There is a new node in the queue No more data will arrive (production has halted)
Using Synchronization Primitives (3) Synchronization actions used by the Dequeuer class
Using Synchronization Primitives (4) • Two synchronized methods: • decrementNumEnqsLeft • used by Enqueuer thread when exiting • getNumEnqsLeft • used by Dequeuer to inspect if production has halted
Problems Faced • Many Dequeuers died before production (Enqueuers) halted. This was due to the queue being empty at some point. The numEnqsLeft variable was introduced to resolve this issue. • Dangling Dequeuer threads: • Decision to wait or quit was not atomic • Dequeuer threads got preempted just before they were about to wait. • Then, Enqueuers added nodes and quit. • Dequeuer threads resume and wait. • But now there are no Enqueuer threads left to notify them. • Solved by expanding the synchronized block so that decision to wait or quit is atomic.
Testing the Implementation Printing output of operations resulted in random orderings Sample: If printing was synchronized, then Enqueue and Dequeue could not proceed concurrently --> defeats the purpose of a two-lock algorithm. Use post simulation sort: - At the end of the simulation, the output is sorted using the *nix sort utility based on the first number on each line. This is the nanoTime (System.nanoTime()) - Result: an accurate schedule of operations.
Performance 120,000 Enqueue operations divided equally among Enqueuer threads
Thank You Questions?