CountdownEvent not waiting for all threads to signal

408 views Asked by At

I've got the following multithreaded code for calculating Euler's number. I'm new in multithreaded programming and maybe I'm missing something. For some reason countdown.Wait() is not waiting for all the threads and totalSum is different almost every time. It looks like it skips some of the intermediate sums.

public static class Program
{
    private static int elementsCount = 500;
    private static int threadsCount = 20;
    private static string outputFileName = "defaultFileName.txt";
    private static bool isInQuietMode = false;

    private static BigRational totalSum = new BigRational(0.0m);

    private static CountdownEvent countDown = new CountdownEvent(threadsCount);
    private static Object locker = new Object();

    private static void Main(string[] args)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        for (int threadIndex = 0; threadIndex < threadsCount; threadIndex++)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(CalculateEulerNumber), threadIndex);
        }

        countDown.Wait();

        File.WriteAllText(outputFileName, "Euler's number: " + totalSum);

        stopwatch.Stop();

        Console.WriteLine("Result: ");
        Console.WriteLine("Total time elapsed - " + stopwatch.Elapsed);
        if (!isInQuietMode)
        {
            Console.WriteLine("Euler's number - " + totalSum);
        }
    }

    private static void CalculateEulerNumber(object threadIndexObject)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        int threadIndex = Convert.ToInt32(threadIndexObject);

        BigRational sum = new BigRational(0.0m);

        for (int k = threadIndex; k < elementsCount; k += threadsCount)
        {
            BigRational numerator = BigRational.Pow((3 * k), 2) + 1;
            BigRational denominator = Factorial(3 * k);

            sum += BigRational.Divide(numerator, denominator);
        }

        totalSum = BigRational.Add(totalSum, sum);

        stopwatch.Stop();

        lock (locker)
        {
            int threadNumber = threadIndex + 1;

            Console.WriteLine("Тhread " + threadNumber + ": ");
            Console.WriteLine("Time elapsed - " + stopwatch.Elapsed);

            if (!isInQuietMode)
            {
                Console.WriteLine("Intermediate sum - " + sum.ToDecimalString(40));
            }

            Console.WriteLine();
        }

        countDown.Signal();
    }

    private static BigRational Factorial(int n)
    {
        BigRational factorial = 1;

        for (int i = 1; i <= n; i++)
        {
            factorial *= i;
        }

        return factorial;
    }
}
2

There are 2 answers

0
Alexander Bell On

@usr made a good point: you better use ConcurrentStack<T> or ConcurrentQueue<T> as detailed in http://msdn.microsoft.com/en-us/library/system.collections.concurrent%28v=vs.110%29.aspx. Also, it's better to implement your algorithm using Task.Factory as explained by Alexandra Rusina in http://blogs.msdn.com/b/csharpfaq/archive/2010/06/01/parallel-programming-in-net-framework-4-getting-started.aspx. As per the mentioned resources, your solution may look like something following (giving you general idea)

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

ConcurrentStack<int> cs = new ConcurrentStack<int>();

        public static double YourFunction(int SomeNumber)
        {
            // computation of result
            return result;
        }

        private void start_Click(object sender, RoutedEventArgs e)
        {
            textBlock1.Text = "";
            label1.Content = "Milliseconds: ";

            var watch = Stopwatch.StartNew();
            List<Task> tasks = new List<Task>();
            for (int i = 2; i < 20; i++)
            {
                int j = i;
                var t = Task.Factory.StartNew(() =>
                {
                    int result = YourFunctiopn(j);
                    this.Dispatcher.BeginInvoke(new Action(() =>
                         cs.Add(result ))
                    , null);
                });
                tasks.Add(t);
            }

            Task.Factory.ContinueWhenAll(tasks.ToArray(),
                  result =>
                  {
                      var time = watch.ElapsedMilliseconds;
                      this.Dispatcher.BeginInvoke(new Action(() =>
                          label1.Content += time.ToString()));
                  });

        }
    }

Hope this will help. Rgds,

2
Avneesh On

You are using the CountDownEvent wrongly.CountDownEvent are for signalling and you dont need this in current program. You can do this with tasks:

 public class Class1
{
    private static int elementsCount = 500;
    private static int threadsCount = 20;
    private static string outputFileName = "defaultFileName.txt";
    private static bool isInQuietMode = false;
    private static BigRational totalSum = new BigRational(0.0m);

    public static void Main1(string[] args)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        List<Task<BigRational>> tasks = new List<Task<BigRational>>();

        //Create the tasks
        for (int threadIndex = 0; threadIndex < threadsCount; threadIndex++)
        {
            Task<BigRational> task = new Task<BigRational>((data)=>
            {
                return CalculateEulerNumber(data);

            },threadIndex);
            tasks.Add(task);
        }
        foreach (var task in tasks)
        {
            task.Start();
        }
        //Wait for tasks
        Task.WaitAll(tasks.ToArray());

        //Add the results
        foreach (var task in tasks)
        {
            totalSum = BigRational.Add(totalSum, task.Result); 
        }
        File.WriteAllText(outputFileName, "Euler's number: " + totalSum);

        stopwatch.Stop();

        Console.WriteLine("Result: ");
        Console.WriteLine("Total time elapsed - " + stopwatch.Elapsed);
        if (!isInQuietMode)
        {
            Console.WriteLine("Euler's number - " + totalSum);
        }
    }

    private static BigRational CalculateEulerNumber(object threadIndexObject)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        int threadIndex = Convert.ToInt32(threadIndexObject);

        BigRational sum = new BigRational(0.0m);

        for (int k = threadIndex; k < elementsCount; k += threadsCount)
        {
            BigRational numerator = BigRational.Pow((3 * k), 2) + 1;
            BigRational denominator = Factorial(3 * k);

           sum += BigRational.Divide(numerator, denominator);
        }
        stopwatch.Stop();
         int threadNumber = threadIndex + 1;

           Console.WriteLine("Тhread " + threadNumber + ": ");
           Console.WriteLine("Time elapsed - " + stopwatch.Elapsed);

            if (!isInQuietMode)
            {
                Console.WriteLine("Intermediate sum - " + sum.ToString());
            }

           Console.WriteLine();
        return sum;
    }

    private static BigRational Factorial(int n)
    {
        BigRational factorial = 1;

        for (int i = 1; i <= n; i++)
        {
            factorial *= i;
        }

        return factorial;
    }
}

So create the task and each task can run seperately and return individual sum. You can then add the results to create the total sum. There is no need of locks either.