Showing posts with label data structures. Show all posts
Showing posts with label data structures. Show all posts

Thread Safe Circular Queue in C#


While completing a screen for a Software Development Engineer in Test (SDTE) position for Microsoft I had to implement a thread safe circular queue.

The question was the following:

Implement a circular queue of integers of user-specified size using a simple array. Provide routines to initialize(), enqueue() and dequeue() the queue. Make it thread safe. A really good observation was: Please do not to use existing class libraries for this question. Thanks!

My first attempt was obviously get more information about a circular queue and bring it back to my mind. That's because I had studied it a long time ago when I was in the beginning of the computer engineering course. If you don't use it you forget it. That's the truth.

This post aims to present an implementation of a thread safe circular queue in C#. I won't deal with theory. For theory, I advise you to read the Wikipedia article about circular queue (buffer) to get started.

I reused some code from the links I present in the references section at the end of this post.

I'll break down the code I implemented so that you can go through the functions that were asked in the problem statement.

class ThreadSafeCircularQueue
{
  private int[] queue;
  private int head;
  private int tail;
  private int length;

  static Object thisLock = new Object();

  public CircularQueue()
  {
     Initialize();
  }

  ...
}

The class ThreadSafeCircularQueue has five properties: an integer array (the queue) and the queue's head, tail and length. A static object thisLock is used to make the queue thread safe.

private void Initialize()
{
  head = tail = -1;

  Console.Write("Circular Queue size: ");

  string length = Console.ReadLine();

  this.length = int.Parse(length);

  queue = new int[this.length];
}

The function Initialize() does what its name means. The queue's head and tail are set to appropriate values and the user has the opportunity of specifying the queue's size (length) by entering an integer value. A new queue is then created and has the user specified size.

The Enqueue() and Dequeue() functions shown bellow do the hard work, that is, they control all the circular queue functionality.

The Enqueue() function receives an integer value to be enqueued. Note the use of the thisLock variable. It is used so that we get thread safety, that is, all the code section inside the lock statement can't be executed by two threads at the same time. This avoids the problem of two concurrent threads trying to access the queue data structure simultaneously. If it's not controlled in the source code level we can get invalid values in the queue what could lend to a corrupted queue. That's not what is expected. When the code section that is inside the lock statement is being executed by a thread and other thread reaches the same section of code, this second thread waits till the first thread exits such code section. This way the queue has a valid set of values and its data is maintained secure.

Firstly is checked if the queue is full. It is full when it's head points to index 0 and its tail points to length - 1 or when its tail + 1 equals the same value of head. Remember that it is a circular queue.

In case the queue is full a message is shown to the user. If it still has room we check if the "end" of the queue (tail) that points to the last element has an index of length minus 1. If it is then the tail will now point to the the "start" of the queue. The other possibility is to increment the tail variable. The position referenced by the tail variable is then used to enqueue the value parameter into the queue. Then the just enqueued value is printed in the screen. A check is done to verify if the head variable has a value of -1. If it's the case then head will point to index 0.

private void Enqueue(int value)
{
  lock(thisLock)
  {
    if((head == 0 && tail == length - 1)  (tail + 1 == head))
    {
      Console.WriteLine("Circular queue is full.");

      return;
    }
    else
    {
      if(tail == length - 1)
        tail = 0;
      else
        tail++;

      queue[tail] = value;

      Console.WriteLine("In -> {0}", value);
    }

    if(head == -1)
      head = 0;
  }
}

The Dequeue() function also uses the lock statement because it must be a thread safe function.

Firstly is checked if the queue's head points to -1. Remember that in the Initialize() function when the queue is initialized its head and tail variables are set to -1. If head has a value of -1 then the queue is empty and the user is informed about that. A value of -1 is returned to conform with the function signature that needs to return an integer value.

If the queue is not empty the integer variable v receives the value that is indexed in the queue's head position. The value 0 is then assigned to the position that was just dequeued.

Now it's necessary to update the head position inside the queue. To accomplish that it's checked if head is equal tail, if it is, then head = tail = -1. This last operation states that the queue is empty after the dequeue operation. Else it's checked if head is equal length - 1. If it is the case then head receives 0. The last possible condition increments the value of head. In the end the value being dequeued (v) is printed in the screen.

Printing the values in the screen is a good way of keeping track of what is going on while the program is running.

private void Dequeue()
{
  lock(thisLock)
  {
    int value;

    if(head == -1)
    {
      Console.WriteLine("Circular queue is empty.");

      value = -1;
    }
    else
    {
      value = queue[head];
      queue[head] = 0;

      if(head == tail)
        head = tail = -1;
      else
        if(head == length - 1)
          head = 0;
        else
          head++;
    }

    Console.WriteLine("Out -> {0}", value);
  }
}

Now take a look at the Show() function bellow. Again the lock is present. Why? In an instant you'll get to the point. Hold your horses! :-)

A check is done to verify if the queue is empty. If it is the case the callee function just returns to the caller function. If it is not the case we proceed to show the values already present in the queue. If tail is less than head a for loop starting in 0 and ending in length - 1 iterates the queue and each value is written in the screen. Otherwise a for loop starting in 0 end ending in tail iterates the queue and each value is written in the screen.

private void Show()
{
  lock(thisLock)
  {
    int i;

    if(head == -1)
    {
      Console.WriteLine("Circular queue is empty.");

      return;
    }
    else
    {
      if(tail < head)
      {
        //for(i = head; i <= size - 1; i++)
        for(i = 0; i <= length - 1; i++)
          Console.Write("{0} ", queue[i]);
      }
      else
        //for(i = head; i <= tail; i++)
        for(i = 0; i <= tail; i++)
          Console.Write("{0} ", queue[i]);

      Console.WriteLine();
    }
  }
}

The following is the EnqueueDequeue() function responsible for calling the Queue(), Dequeue() and Show() functions. In this function I execute a simple test case and comment the operations being carried out.

public void EnqueueDequeue()
{
  Enqueue(1);
  Enqueue(2);
  Enqueue(3);
  Enqueue(4);
  Show();
  Enqueue(5); // Circular queue is full!
  Dequeue();
  Dequeue();
  Dequeue();
  Dequeue();
  Dequeue(); // Circular queue is empty!
  Dequeue(); // Circular queue is empty!
  Show();
  Enqueue(6);
  Show();
  Enqueue(7);
  Show();
  Dequeue();
  Dequeue();
  Enqueue(8);
  Enqueue(9);
  Show();

  // Supposing a 4 size queue:  0 ¦ 0 ¦ 0 ¦ 0
  //
  //                            1 ¦ 0 ¦ 0 ¦ 0  <- Enqueue 1
  //
  //                            1 ¦ 2 ¦ 0 ¦ 0  <- Enqueue 2
  //
  //                            1 ¦ 2 ¦ 3 ¦ 0  <- Enqueue 3
  //
  //                            1 ¦ 2 ¦ 3 ¦ 4  <- Enqueue 4
  //              
  //                            1 ¦ 2 ¦ 3 ¦ 4  <- Circular queue is full when trying to enqueue 5.
  //
  //                            0 ¦ 2 ¦ 3 ¦ 4  <- Dequeue 1
  //
  //                            0 ¦ 0 ¦ 3 ¦ 4  <- Dequeue 2
  //
  //                            0 ¦ 0 ¦ 0 ¦ 4  <- Dequeue 3
  //                   
  //                            0 ¦ 0 ¦ 0 ¦ 0  <- Dequeue 4
  //
  //                            0 ¦ 0 ¦ 0 ¦ 0  <- Circular queue is empty when trying to dequeue.
  //
  //                            0 ¦ 0 ¦ 0 ¦ 0  <- Circular queue is empty when trying to dequeue.
  //
  //                            6 ¦ 0 ¦ 0 ¦ 0  <- Enqueue 6
  //
  //                            6 ¦ 7 ¦ 0 ¦ 0  <- Enqueue 7
  //
  //                            0 ¦ 7 ¦ 0 ¦ 0  <- Dequeue 6
  //
  //                            0 ¦ 0 ¦ 0 ¦ 0  <- Dequeue 7
  //
  //                            8 ¦ 0 ¦ 0 ¦ 0  <- Enqueue 8
  //
  //                            8 ¦ 9 ¦ 0 ¦ 0  <- Enqueue 9
  //
  // * 0 is set in a position when dequeueing so that it's easier to watch the queue variable.
}

Now the main start thread:

class Program
{
  static void Main(string[] args)
  {
    ThreadSafeCircularQueue circularQueue = new ThreadSafeCircularQueue();

    Thread[] threads = new Thread[10];

    for(int i = 0; i < threads.Length; i++)
    {
      threads[i] = new Thread(new ThreadStart(circularQueue.EnqueueDequeue));
    }

    for(int i = 0; i < threads.Length; i++)
    {
      threads[i].Start();
    }

    Console.ReadLine();
  }
}

As you can see above I declare an object of type ThreadSafeCircularQueue. An array of 10 objects of type Thread is then created. In a for loop I instantiate each Thread passing to it a delegate that represents the method that'll be invoked when the thread begins executing.

In the subsequent for loop I call the Start() method of each thread and they start executing simultaneously, tough they won't concur when accessing the functions Enqueue(), Dequeue() and Show().

Visual Studio C# Console Application
You can get the Microsoft Visual Studio Project and the app executable at: http://leniel.googlepages.com/ThreadSafeCircularQueueCSharp.zip

References
During the research phase of this implementation I recurred to some sites to get more information regarding the circular queue data structure. The following list can provide you a better understanding of such a data structure.

Thread Safety articles
[1] Venners, Bill. Designing for Thread Safety: Using Synchronization, Immutable Objects, and Thread-Safe Wrappers. 1998. Available at <http://www.artima.com/designtechniques/threadsafety.html>. Accessed on April 29, 2008.

[2] Suess, Michael. A Short Guide to Mastering Thread-Safety. 2006. Available at <http://www.thinkingparallel.com/2006/10/15/a-short-guide-to-mastering-thread-safety/>. Accessed on April 29, 2008.

[3] Allen, K. Scott. Statics & Thread Safety: Part II. 2004. Available at <http://www.odetocode.com/Articles/314.aspx>. Accessed on April 29, 2008.

Circular Queue sample code
[4] Kumar, Nunna Santhosh. Circular Queue Implementation using Arrays in C++. Available at <http://www.sourcecodesworld.com/source/show.asp?ScriptID=887>. Accessed on April 29, 2008.

Thread material
[5] Albahari, Joseph. Threading in C#. 2007. Available at <http://www.albahari.com/threading/>. Accessed on April 29, 2008.

Breadth and depth first search - part 3


As an extra, today I'm posting the C++ code of the breadth and depth first search algorithms. Take a look at part 1 and part 2 of this series.

When I had to hand in the work for the artificial intelligence discipline, the teacher wanted the code in C++ and I had already started developing the code in C#. The result was two versions of the same functions. The good part is that I could master both languages while developing such a code.

The code presented here uses an adjacency matrix to represent the links between the cities that are part of the Romania map shown bellow.

The following is the adjacency matrix:

// Adjacency matrix
int map[21][21] = {
/*   A B C D E F G H I L M N O P R S T U V Z */
  {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0},
  {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1}, // Arad
  {2,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,1,0,0}, // Bucharest
  {3,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0}, // Craiova
  {4,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0}, // Dobreta
  {5,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0}, // Eforie
  {6,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0}, // Fagaras
  {7,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, // Girgiu
  {8,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0}, // Hirsova
  {9,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0}, // Iasi
  {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0}, // Lugoj
  {1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}, // Mehadia
  {2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0}, // Neamt
  {3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1}, // Oradea
  {4,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0}, // Pitesti
  {5,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0}, // Rimnicu Vilcea
  {6,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,0}, // Sibiu
  {7,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0}, // Timisoara
  {8,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0}, // Urziceni
  {9,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0}, // Vaslui
  {0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}  // Zerind
};

Note that the first commented line represents the initial letter of each city's name. The mapping done with the adjacency matrix refers to these letters so that it's easier to understand. For example, getting the first entry of the adjacency matrix that refers to Arad: we have that Arad has paths that lead us to Sibiu, Timisoara and Zerind, thus we put a value of 1 on the columns that represent those cities, in this case, the columns beneath the letters S, T and Z. That's how the mapping is done. We put a value of 0 on the other columns to state that there is no path that leads us to those cities.

The code also has a hand made version of the stack and queue data structures. Each one of these structures is on its proper header file and are inline functions. See their implementations:

// Queue
struct Queue
{
  int start, end, tot;

  int info[max + 1];
};

void StartQueue(Queue *q)
{
  q->tot = 0;

  q->start = 1;

  q->end = 0;
}

int IsQueueEmpty(Queue *q)
{
  return q->tot == 0 ? 1 : 0;
}

int IsQueueFull(Queue *q)
{
  return q->tot == max ? 1 : 0;
}

int Adc(int x)
{
  return x == max ? 1 : x + 1;
}

void Enqueue(Queue *q, int x)
{
  if(!IsQueueFull(q))
  {
    q->end = Adc(q->end);

    q->info[q->end] = x;

    q->tot++;
  }
}

int Dequeue(Queue *q)
{
  int ret = 0;

  if(!IsQueueEmpty(q))
  {
    ret = q->info[q->start];
  
    q->start = Adc(q->start);
  
    q->tot--;
  }

  return ret;
}
// End Queue
// Stack
struct Stack
{
  int topo;

  int info[max + 1];
};

void StartStack(Stack *s)
{
  s->topo = 0;
}

int IsStackEmpty(Stack *s)
{
  return s->topo==0 ? 1 : 0;
}

int IsStackFull(Stack *s)
{
  return s->topo == max ? 1 : 0;
}

void Push(Stack *s, int x)
{
  if(!IsStackFull(s))
  {
    s->topo++;
  
    s->info[s->topo] = x;
  }
}

int Pop(Stack *s)
{
  int ret = 0;

  if(!IsStackEmpty(s))
  {
    ret = s->info[s->topo];
  
    s->topo--;
  }

  return ret;
}
// End Stack

The Breadth First Search and Depth First Search functions are written in the same fashion of the C# code, but with little modifications.

void BreadthFirstSearch(int origin, int destination)
{
  Queue *q = new Queue();

  StartQueue(q);

  Enqueue(q, origin);

  while(IsQueueEmpty(q) == 0)
  {
    int u = Dequeue(q);

    if(u == destination)
    {
      printf("Path found.");

      break;
    }
    else
    {
      visited[u] = 1;

      for(int v = 1; v <= 20; v++)
      {
        if(map[u][v] != 0)
        {
          if(visited[v] == 0)
          {
            visited[v] = 1;

            parents[v] = u;

            if(v != destination)
            {
              if(!IsQueueFull(q))
              {
                Enqueue(q, v);

                ShowPath(v);

                printf("\n");
              }
              else
              {
                printf("Queue full.");

                break;
              }
            }
            else
            {
              ShowPath(v);

              return;
            }
          }       
        }
      }
    }
  }
}
void DepthFirstSearch(int origin, int destination)
{
  Stack *s = new Stack();

  StartStack(s);

  Push(s, origin);

  while(IsStackEmpty(s) == 0)
  {
    int u = Pop(s);

    if(u == destination)
    {
      printf("Path found.");

      break;
    }
    else
    {
      visited[u] = 1;

      for(int v = 1; v <= 20; v++)
      {
        if(map[u][v] != 0)
        {
          if(visited[v] == 0)
          {
            visited[v] = 1;

            parents[v] = u;

            if(v != destination)
            {
              if(!IsStackFull(s))
              {
                Push(s, v);

                ShowPath(v);

                printf("\n");
              }
              else
              {
                printf("Stack full.");

                break;
              }
            }
            else
            {
              ShowPath(v);

              return;
            }
          }      
        }
      }
    }
  }
}

To show the travelled paths there is a recursive function called ShowPath:

void ShowPath(int u)
{
  if(parents[u] != 0)
    ShowPath(parents[u]);

  printf(" %s", cities[u]);
}

You see, I had finalized the C# code and even sent the code to the teacher, but I received his reply stating that he wanted the code in C++. I complained with him! I told him about the easiness that modern programming languages as C# offers us when writing code.

Today, the data structures (queue and stack) "hand made" in this code are present in modern and optimized fashions inside standard libraries. We just need to instantiate an object from that specific class, in this case, stack or queue, and tada, we get lots of functions that do the hard work. But the point here is not the easiness. What the teacher wanted to force us to do was to comprehend how those data structures really function.

Nothing is better than writing the data structures by yourself. Although I didn't agree at the time, I thank him for forcing me to learn. Needless to say, these are basic data structures and are used in a great amount of code. You'll see these data structures during your entire developer life.

Visual Studio C++ Console Application
You can get the Microsoft Visual Studio Project and the app executable at:

http://leniel.googlepages.com/BreadthDepthFirstSearchCPlusPlus.zip