Channels ▼
RSS

C/C++

C++/CLI Threading: Part II


C++/CLI supports the ability to create multiple threads of execution within a single program. Last month, we saw how threads are created and synchronized. This month, we'll see how shared variables can be guarded against compromise during concurrent operations, learn about thread-local storage, and we'll look at interlocked operations.

Other Forms of Synchronization

We can control synchronization of threads directly by using a number of functions in classes Monitor and Thread. Listing One contains an example.

Listing One

using namespace System;
using namespace System::Threading;

int main()
{
/*1*/   MessageBuffer^ m = gcnew MessageBuffer;

/*2a*/  ProcessMessages^ pm = gcnew ProcessMessages(m);
/*2b*/  Thread^ pmt = gcnew Thread(gcnew ThreadStart(pm,
            &ProcessMessages::ProcessMessagesEntryPoint));
/*2c*/  pmt->Start();

/*3a*/  CreateMessages^ cm = gcnew CreateMessages(m);
/*3b*/  Thread^ cmt = gcnew Thread(gcnew ThreadStart(cm, 
            &CreateMessages::CreateMessagesEntryPoint));
/*3c*/  cmt->Start();

/*4*/   cmt->Join();
/*5*/   pmt->Interrupt();
/*6*/   pmt->Join();

    Console::WriteLine("Primary thread terminating");
}

public ref class MessageBuffer 
{
    String^ messageText;
public:
    void SetMessage(String^ s)
    {
/*7*/       Monitor::Enter(this);
            messageText = s;
/*8*/       Monitor::Pulse(this);
            Console::WriteLine("Set new message {0}", messageText);
            Monitor::Exit(this);
    }

    void ProcessMessages()
    {
/*9*/       Monitor::Enter(this);
            while (true)
            {
                try
                {
/*10*/              Monitor::Wait(this);
                }
            catch (ThreadInterruptedException^ e)
                {
            Console::WriteLine("ProcessMessage interrupted");
                return;
            }

            Console::WriteLine("Processed new message {0}", messageText);
        }
        Monitor::Exit(this);
    }
};

public ref class CreateMessages
{
    MessageBuffer^ msg;
public:
    CreateMessages(MessageBuffer^ m)
    {
        msg = m;
    }

    void CreateMessagesEntryPoint()
    {
        for (int i = 1; i <= 5; ++i)
        {
            msg->SetMessage(String::Concat("M-", i.ToString()));
            Thread::Sleep(2000);
        }
        Console::WriteLine("CreateMessages thread terminating");
    }
};

public ref class ProcessMessages
{
    MessageBuffer^ msg;
public:
    ProcessMessages(MessageBuffer^ m)
    {
        msg = m;
    }

    void ProcessMessagesEntryPoint()
    {
        msg->ProcessMessages();
        Console::WriteLine("ProcessMessages thread terminating");
    }
};

In case 1, a shared buffer of type MessageBuffer is created. In cases 2a, 2b, and 2c, a thread is created and started such that it processes each message placed in that buffer. Cases 3a, 3b, and 3c create and start a thread that causes a series of five messages to be put into the shared buffer for processing. The two threads are synchronized such that the processor can't process the buffer until the creator has put something there, and the creator can't put another message there until the previous one has been processed. In case 4, we wait until the creator thread has completed its work.

By the time case 5 executes, the processor thread should have processed all of the messages the creator put there, so we tell it to stop work by interrupting it using Thread::Interrupt. We then wait on that thread in case 6 by calling Thread::Join, which allows the calling thread to block itself until some other thread terminates. (Instead of waiting indefinitely, a thread can specify a maximum time that it will wait.)

The CreateMessages thread is quite straightforward. It writes five messages to the shared message buffer, waiting two seconds between each one. To suspend a thread for a given amount of time (in milliseconds), we call Thread::Sleep. A sleeping thread is resumed by the runtime environment rather than by another thread.

The ProcessMessages thread is even simpler because it has the MessageBuffer class do all its work. Class MessageBuffer's functions are synchronized because only one of them at a time can have access to the shared buffer.

The main program starts the processor thread first. As such, that thread starts executing ProcessMessages, which causes the parent object's synchronization lock to be obtained. However, it immediately runs into a call to Wait in case 10, which causes it to wait until it is told to continue; however, it also gives up its hold on the synchronization lock in the meantime, allowing the creator thread to obtain the synchronization lock and to execute SetMessage. Once that function has put the new message in the shared buffer, it calls Pulse in case 8, which allows any one thread waiting on that lock to wake up and resume operation. However, this cannot happen until SetMessage completes execution because it doesn't give up its hold on the lock until that function returns. Once that happens, the processor thread regains the lock, the wait is satisfied, and execution resumes beyond case 10. A thread can wait indefinitely or until a specified amount of time has lapsed. For completeness, the output is shown in Figure 1.

Figure 1: Output of Listing One.

Set new message M-1
Processed new message M-1
Set new message M-2
Processed new message M-2
Set new message M-3
Processed new message M-3
Set new message M-4
Processed new message M-4
Set new message M-5
Processed new message M-5
CreateMessages thread terminating
ProcessMessage interrupted
ProcessMessages thread terminating
Primary thread terminating

Note carefully that the processor thread was started before the creator thread. If they were started in the opposite order, the first message would be added, yet no processor thread would be waiting, so no processor thread is woken up. By the time the processor thread gets to its first call to Wait, it will have missed the first message and will only be woken up when the second one has been stored.

Managing Threads

By default, a thread is a foreground thread that executes until its entry-point function terminates, regardless of the life span of its parent. On the other hand, a background thread automatically terminates when its parent terminates. We configure a thread as being a background thread by setting Thread's property IsBackground. A background thread can also be made a foreground thread by the same approach.

Once a thread has been started, it is alive. We can test for this by inspecting Thread's property IsAlive. A thread can give up the rest of its CPU time slice by calling Wait with a time of zero milliseconds. A thread can get at its own Thread object via the property CurrentThread::Thread::CurrentThread.

Each thread has a priority level associated with it and this is used by the runtime environment to schedule the execution of threads. A thread's priority can be set or tested via the property Thread::Priority. Priorities range from ThreadPriority::Lowest to ThreadPriority::Highest. By default, a thread has priority ThreadPriority::Normal. Because thread scheduling varies from one implementation to another, we should not rely too heavily on priority levels as a means of controlling threads.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.