Create multiple threads to see if work is done simultaneously

When you hear about threads and threading, you hear that they allow concurrent execution of code, which is supposed to lead to better performance. Most computers nowadays have more than one CPU, meaning that each CPU can be executing code at the same time.

Hit Ctrl-Shift-Escape to bring up Task Manager, then switch to the Performance tab.

Here in CPU Usage history, my 2 year old Dell laptop shows 4 separate panels and thus 4 CPUS.

If you have an infinite loop in 1 thread, then the CPU usage will be 100% for that thread, but there are 4 CPUs, so the CPU usage will show 25% if nothing else is running on the machine.

clip_image002

Task manager also shows that 115 separate processes are running. That means 115 separate programs. Some of them were started by me, others are part of the operating system. Click on the Processes tab to see a list of them.

Process Explorer is a free download and shows a lot more detail.

Below is a program that creates a user specified number of threads.

Start Visual Studio 2010, File->New Project-> C#->WPF Application

Paste in the code below to replace the contents of MainWindow.Xaml.cs.

If your project name isn’t WpfApplication1, adjust the Namespace.

Run the code by hitting Ctrl-F5 (or F5 to run it under the VS debugger).

The code creates some controls, like labels and textboxes and a button. These allow you to set various parameters when calling DoWork, which creates a number of threads, each running the same code.

DoWork creates a single List to hold strings created from each thread. Each thread then adds some status text to the list.

A CountDownEvent is used to signal when all the threads are done, and the main thread waits for them.

With the default parameters, run the code and see what the output is. Does it appear that the code in each thread runs? Scroll down the list view to see.

Does it appear that some work is being done in each thread simultaneously? Interleaved results would result, no?

Depending on your machine, you might not see any interleaving. Try changing the amount of work each thread does, by changing the iteration count. Try changing the number of threads. What happens if you change the Sleep to non-zero?

You can use Task Manager’s Process pane to see the number of threads per process. View->Select Columns->Threads. However, since this is a sampled value, you might not see the maximum you expect.

<code>

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Threading;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            WindowState = System.Windows.WindowState.Maximized;
            // rather than adding individual methods and hooking them up in the XAML, 
            // add event handling here. This way, the XAML can be unmodified and folks pasting
            // from the web will be able to run the code more easily.
            this.Loaded += (oLoad, eLoad) =>
            {
                var txtNThreads = new TextBox()
                {
                    Text = "10",
                    ToolTip = "# Threads",
                    Width = 50
                };
                var lblNThreads = new Label()
                {
                    Content = "# of _Threads to create",
                    Target = txtNThreads
                };

                var txtNItersPerThread = new TextBox()
                {
                    Text = "10",
                    ToolTip = "# Iterations per Thread",
                    Width = 50
                };
                var lblNItersPerThread = new Label()
                {
                    Content = "# of _Iterations per thread",
                    Target = txtNItersPerThread
                };
                
                var txtNSleep = new TextBox()
                {
                    Text = "0",
                    ToolTip = "_mSecs to sleep in thread",
                    Width = 50,
                };
                var lblNSleep = new Label()
                {
                    Content = "_mSecs to sleep",
                    Target = txtNSleep
                };
                var btnRefresh = new Button()
                {
                    Content = "_Refresh",
                    MaxWidth = 100
                };

                var spHoriz = new StackPanel()
                {
                    Orientation = System.Windows.Controls.Orientation.Horizontal,
                    MaxHeight = 25
                };
                spHoriz.Children.Add(lblNThreads);
                spHoriz.Children.Add(txtNThreads);
                spHoriz.Children.Add(lblNItersPerThread);
                spHoriz.Children.Add(txtNItersPerThread);
                spHoriz.Children.Add(lblNSleep);
                spHoriz.Children.Add(txtNSleep);
                spHoriz.Children.Add(btnRefresh);
                var spVert = new StackPanel()
                {
                    Orientation = System.Windows.Controls.Orientation.Vertical
                };
                spVert.Children.Add(spHoriz);
                var dp = new DockPanel();
                spVert.Children.Add(dp);
                this.Content = spVert;

                btnRefresh.Click += (oBtn, eBtn) =>
                {
                    try
                    {
                        var result = DoWork(
                            int.Parse(txtNThreads.Text),
                            int.Parse(txtNItersPerThread.Text),
                            int.Parse(txtNSleep.Text)
                            );

                        var lv = new ListView()
                        {
                            ItemsSource = result,
                            MaxHeight = this.ActualHeight - 50
                        };
                        dp.Children.Clear();
                        dp.Children.Add(lv);
                    }
                    catch (Exception ex)
                    {
                        dp.Children.Clear();
                        dp.Children.Add(new TextBlock() { Text = ex.ToString() });
                    }
                };
                btnRefresh.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
            };
        }
        List<string> DoWork(int nThreads,int nItersPerThread, int nSleep)
        {
            var thrdWait = new CountdownEvent(nThreads);

            var strStatus = new List<string>();
            strStatus.Add("Starting");
            var startTime = DateTime.Now;
            for (int i = 0; i < nThreads; i++)
            {
                var hThread = new Thread((object s) =>
                {
                    // this is the code that's run per threads
                    strStatus.Add("thread " + s.ToString() + " starting " + DateTime.Now.ToString());
                    for (int j = 0; j < nItersPerThread ; j++)
                    {
                        lock (strStatus)
                        {
                            var nMsecs = DateTime.Now - startTime;
                            strStatus.Add(string.Format("{0,5:} {1,5} {2}", s, j, nMsecs));
                        }
                        if (nSleep > 0)
                        {
                            Thread.Sleep(100);
                        }
                    }
                    strStatus.Add("thread " + s.ToString() + "   ending");
                    thrdWait.Signal();
                });
                hThread.Start(i);
            }
            thrdWait.Wait();
            strStatus.Add("Result from main thread");
            return strStatus;
        }
    }
}

</code>