Sie sind auf Seite 1von 63

COMP 150-CCP

Concurrent Programming

Lecture 21:
Concurrency in Other
Environments (Part 1)
Dr. Richard S. Hall

rickhall@cs.tufts.edu

Clement Escoffier

clement.escoffier@gmail.com

Concurrent programming April 8th, 2008

Why Use Java for the Course?

Java is widely popular


Java treats concurrency as a first-class concept

It is not tacked on as an afterthought


It is reasonably simple to use the concurrency
mechanisms without having to deal with extra cruft

Java is available and works well across many


platforms

Makes it accessible to students on their preferred


computing environment

Why Not Java?

Java is not suited to all tasks


Other approaches are potentially more innovative
((e.g., Erlang)
Other languages are popular (e.g., C/C++)
O
Other platforms are popular (e.g., Linux,
Win32, .NET)
W
The last two bullets will be the focus of
this lecture to provide some perspective

Agenda

Linux, C/C++, and Pthreads


Win32 and C/C++
.NET and C#

Agenda

Linux, C/C++, and Pthreads


Derived from
https://computing.llnl.gov/tutorials/pthreads/

What are Pthreads?

Different platforms have their own proprietary


versions of threads

Implementations differed substantially making it


difficult to develop portable threaded application

A standardized threading API was required

For UNIX-based systems, this interface has been


specified by the IEEE POSIX 1003.1c threads
standard (aka Pthreads))

A set of C language programming types and procedure calls

Pthreads Recommendation

Be careful if your application uses libraries or


other objects that do not explicitly guarantee
thread safety
When in doubt, assume that they are not threadsafe until proven otherwise

This can be done by serializing the calls to the


uncertain routine

Pthreads Overview

The subroutines which comprise the Pthreads


API can be grouped into three major classes

Thread management

Mutex management

These functions work directly on threads


e.g., creating, detaching, and joining threads
These functions deal with synchronization
e.g., creating, destroying, locking and unlocking mutex locks

Condition variables

These functions address communication between threads


based on programmer specified conditions
e.g., creating, destroying, waiting and signaling condition
variables

Pthreads API Overview (1/2)


P

All library identifiers begin with pthread_

Common prefixes

pthread_ - for threads themselves


pthread_attr_ - for thread attributes
pthread_mutex_ - for mutexes
pthread_mutexattr_ - for mutex attributes
pthread_cond_ - for condition variables
pthread_condattr_ - for condition variable attributes
pthread_key_ - for thread-specific data keys

We will concentrate on threads, mutexes, and


condition variables

Pthreads API Overview (2/2)


P

Pthreads API is centered around the concept of


opaque objects

Pthreads API typically requires both resource


creation and destruction to handle resource
allocation

The basic calls create or modify opaque objects

No garbage collector here

The Pthreads API contains over 60 subroutines


The current POSIX standard is defined only for
the C language

Linux Development with Pthreads

Include pthread.h in your source


Compile using g++ -pthread

Pthreads Thread API


int pthread_create(
pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void*), void *restrict arg);
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
int pthread_attr_getdetachstate(
const pthread_attr_t *attr, int *detachstate);
int pthread_attr_setdetachstate(
pthread_attr_t *attr, int detachstate);
int pthread_join(pthread_t thread, void **value_ptr);
int pthread_detach(pthread_t thread);
void pthread_exit(void *value_ptr);

Thread Creation
p
pthread_create(thread,attr,start_routine,arg)

Thread type: pthread_t


Creates a new thread

Can create any number of new threads


Created threads are peers in a given process

Arguments

thread - opaque, unique thread identifier


attr - opaque object for setting thread attributes
start_routine - routine for the thread to execute
arg - argument passed to the start routine

Thread Attributes
pthread_attr_init(attr);
pthread_attr_destroy(attr);
pthread_attr_getdetachstate(attr,detachstate);
pthread_attr_setdetachstate(attr,detachstate);

Create, set, get, and destroy thread attributes


One specific attribute is whether a thread
joinable or detached

Detached threads can never be joined


Resources for joinable threads are freed when joined

Good practice to specifically create your threads


as either joinable or detached
pthread_detach can detach a joinable thread

Thread Termination
pthread_exit(status);

Stops a thread

Called when a thread is no longer needed


Does not clean up resources
Also needed in main() if it exits before threads

pthread_join(threadid,status);

Allows the calling thread to wait for the specified


thread to exit

Retrieves the exit status from the exiting thread

Simple Thread Example (1/2)


S
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 3
// Routine for our threads
void *BusyWork(void *null) {
int i;
double result=0.0;
// Perform busy work.
for (i=0; i<1000000; i++) {
result = result + (double)random();
}
printf("result = %e\n",result);
// Terminate the thread.
pthread_exit((void *) 0);
}
// Continued on next slide...

Simple Thread Example (2/2)


S
int main () {
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int t;
void *status;
// Create thread joinable attribute.
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// Create the threads.
for(t = 0; t < NUM_THREADS; t++) {
printf("Creating thread %d\n", t);
pthread_create(&thread[t], &attr, BusyWork, NULL);
}
// Destroy the attribute.
pthread_attr_destroy(&attr);
// Join all threads and print exit status.
for(t = 0; t < NUM_THREADS; t++) {
pthread_join(thread[t], &status);
printf("Thread %d exit status = %ld\n", t, (long)status);
}
// Terminate main thread.
pthread_exit(NULL);
}

Miscellaneous Thread API

p
pthread_self()

p
pthread_equal(thread1,thread2)

Returns the identifier of the calling thread


Determines if two threads are equal

p
pthread_yield()

Forces the calling thread to give up the processor

Pthreads Mutex API


int pthread_mutex_init(
pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(
pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

Mutex Creation
p
pthread_mutex_init(mutex,attr)

Mutex type: pthread_mutex_t


Mutexes are created in unlocked state
Mutex attributes can control various properties,
but we don't explore these

Mutex Locking and Unlocking


pthread_mutex_lock(mutex);
pthread_mutex_trylock(mutex);
pthread_mutex_unlock(mutex);

Lock, test a lock, and unlock mutexes


Mutexes are not reentrant

Trying to acquire a lock twice will deadlock

Mutexes are not magical, they are just an


agreement between threads

If a thread doesn't use them, then all bets are off

Simple Mutex Example (1/2)


S
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 3
#define NUM_INCREMENTS

100

int shared; pthread_mutex_t mutex;


void *BusyWork(void *null) {
int tmp;
for (int i = 0; i < 100; i++) {
// Lock.
pthread_mutex_lock(&mutex);
tmp = shared; tmp++;
pthread_yield();
shared = tmp;
// Unlock.
pthread_mutex_unlock(&mutex);
}
// Terminate thread.
pthread_exit((void *) 0);
}
// Continued on next slide...

Simple Mutex Example (2/2)


S
int main () {
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
// Create mutex.
pthread_mutex_init(&mutex, NULL);
// Create thread joinable attribute.
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// Create the threads.
for(int t = 0; t < NUM_THREADS; t++)
f
pthread_create(&thread[t], &attr, BusyWork, NULL);
// Destroy the attribute.
pthread_attr_destroy(&attr);
// Join all threads.
for(int t = 0; t < NUM_THREADS; t++)
f
pthread_join(thread[t], NULL);
if (shared != (NUM_THREADS * NUM_INCREMENTS))
i
printf("Shared value is incorrect = %d\n", shared);
// Destroy the mutex.
pthread_mutex_destroy(&mutex);
// Terminate main thread.
pthread_exit(NULL);
}

Pthreads Condition Variable API


int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_destroy(pthread_cond_t *cond);

Condition Variable Creation


pthread_cond_init(cond,attr);

Condition variable type: pthread_cond_t


Condition variables allow threads to
communicate and wait for certain conditions

Just like in Java

We don't explore condition variable attributes

Condition Variable Waiting


pthread_cond_wait(cond,mutex);
pthread_cond_timedwait(
cond,mutex,abstime);

Must specify the mutex associated with the


condition variable when waiting

Must be called when mutex is locked by calling thread

Can wait indefinitely or for a certain amount of


time
Similar to Java, you should wait in a loop in case
of a spurious wake up

Condition Variable Signaling


pthread_cond_signal(cond);
pthread_cond_broadcast(cond);

Can signal a single thread or broadcast to all


threads waiting on the condition variable
Must hold the lock to signal

Should release the lock after signaling

Bounded Buffer Example (1/7)


B
#include <iostream>
#include <string>
#include <pthread.h>
using namespace std;
class BoundedBuffer {
private:
// Encapsulate mutex and condition
// variable.
pthread_mutex_t mutex;
pthread_cond_t condvar;
string *buffer;
int max, in, out, count;
public:
BoundedBuffer(int m);
~BoundedBuffer();
void put(string s);
string get();
};

Bounded Buffer Example (2/7)


B
BoundedBuffer::BoundedBuffer(int m) {
max = m;
buffer = new string[max];
in = out = count = 0;
// Initialize mutex and condition variable.
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&condvar, NULL);
}
BoundedBuffer::~BoundedBuffer() {
// Destroy mutex and condition variable.
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&condvar);
}

Bounded Buffer Example (3/7)


B
void BoundedBuffer::put(string s) {
// Acquire lock.
pthread_mutex_lock(&mutex);
// Wait for there to be space.
while (count == max) {
pthread_cond_wait(&condvar, &mutex);
}
cout << "PUT ";
for (int i = 0; i < count; i++) {
cout << " " << buffer[(i + out) % max] << " ";
}
cout << "[" << s << "]" << endl;
buffer[in] = s;
++count;
in = (in + 1) % max;

// Broadcast and unlock.


pthread_cond_broadcast(&condvar);
pthread_mutex_unlock(&mutex);

Bounded Buffer Example (4/7)


B
string BoundedBuffer::get() {
// Acquire lock.
pthread_mutex_lock(&mutex);
// Wait for data.
while (count == 0) {
pthread_cond_wait(&condvar, &mutex);
}
string s = buffer[out];
buffer[out] = "";
--count;
out = (out + 1) % max;
cout << "GET [" << s << "] ";
for (int i = 0; i < count; i++) {
cout << buffer[(i + out) % max] << "
}
cout << endl;

// Broadcast and unlock.


pthread_cond_broadcast(&condvar);
pthread_mutex_unlock(&mutex);
return s;

";

Bounded Buffer Example (5/7)


B

// Produce items to consume.


void * producer(void *v) {
BoundedBuffer *pBuffer = (BoundedBuffer *) v;
string alphabet = "abcdefghijklmnopqrstuvwxyz";
int ai = 0;
while (true) {
pBuffer->put(alphabet.substr(ai, 1));
ai = (ai + 1) % 26;
usleep(1000);
}
}

Bounded Buffer Example (6/7)


B

// Consume produced items.


void * consumer(void *v) {
while (true) {
BoundedBuffer *pBuffer = (BoundedBuffer *) v;
string s = pBuffer->get();
usleep(1000);
}
}

Bounded Buffer Example (7/7)


B
int main() {
// Create shared buffer.
BoundedBuffer *pBuffer = new BoundedBuffer(5);
// Create detached thread attribute.
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// Create producer and consumer threads.
pthread_t p, c;
pthread_create(&p, &attr, producer, pBuffer);
pthread_create(&c, &attr, consumer, pBuffer);
// Destroy attribute.
pthread_attr_destroy(&attr);

// Terminate main thread.


pthread_exit(NULL);

Bounded Buffer Issue

No garbage collection in C/C++ makes some


things more difficult

We would have to worry about freeing the allocated


bounded buffer if the producer and consumer threads
eventually terminate, but the program continues

In Java we could just ignore this issue

Pthreads & C++

The Pthreads API was designed for C; however,


integrating it with C++ is reasonably
straightforward
The next few slides present a simple thread
class in C++

http://www.oopweb.com/CPP/Documents/CPPHOWT
O/Volume/C++Programming-HOWTO-18.html

Thread Class Definition


class Thread
{
public:
Thread();
int Start(void *arg);
protected:
int Run(void *arg);
static void * EntryPoint(void*);
virtual void Setup();
virtual void Execute(void*);
void * Arg() const { return arg_; }
void Arg(void* a) { arg_ = a; }
private:
pthread_t threadId_;
void * arg_;
};

Thread Class Implementation


Thread::Thread() {}
int Thread::Start(void *arg) {
Arg(arg); // store user data
int code = pthread_create(
&threadId_, NULL, Thread::EntryPoint, this);
return code;
}
void * Thread::EntryPoint(void *pthis) { // static
Thread * pthread = (Thread *) pthis;
pthread->Run(Arg());
}
int Thread::Run(void *arg) {
Setup();
Execute(arg);
}
virtual void Thread::Setup() { /* Override */ }
virtual void Thread::Execute(void *arg) { /* Override */ }

Thread Class Usage

Each object is for a single thread


Thread does not exist until Start() is called
To use, create a subclass

The Setup() method can be overloaded to perform


any setup before the thread runs
The Execute() method must be overloaded to
perform the function of the thread

Notice that the thread requires a static function

We get around this by passing in this as the


argument to the thread, which casts it back to the
Thread object so that it can invoke Run()
R

Pthreads Summary

Pthreads gives us a portable threading API for


UNIX-based systems
We only covered the basics in this lecture

However, it is actually fairly simple and there isn't


much more

Pthreads is oriented toward C, but works


reasonably well with C++

Agenda

Win32 and C/C++

Thread on Windows

Very very useful

Imagine a game without threads


All Windows-based GUI use threads

Different thread libraries

The old-fashion
MFC (Microsoft Foundation Class)
OpenThread (since Windows 98)
New improvements in Vista

Example of Threads in Windows

The windows explorer example

Threads can be viewed with Process Explorer


One Browse Thread per Windows Explorer
windows

Thread Scheduling

Win32 threads are scheduled according to the


round-robin principle

This includes multiprocessor machines


Windows stays in control of how long each thread
gets executed

In a cycle, each thread gets allocated a time


slice
Threads can have different priorities

Thread System Overview

The Win32 Thread API can be grouped into


three major classes

Thread management

Mutex management

These functions work directly on threads


e.g., creating, detaching, and joining threads
These functions deal with synchronization
e.g., creating, destroying, locking and unlocking mutex locks

Condition variables

These functions address communication between threads


based on programmer specified conditions
e.g., creating, destroying, waiting and signaling condition
variables

Thread Library

Header:

include <windows.h>

It uses directly kernel32.h

Thread Creation
HANDLE CreateThread(
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in DWORD dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadIdjl
);

Arguments

lpThreadAttributes Security parameter ... NULL

dwStackSize Stack Size, can be NULL (default size set by Windows)

lpStartAddress Function pointer on you Thread run method

lpParameter - The parameter to be passed to the thread procedure

DwCreationFlags - The creation flags (0 or CREATE_SUSPENDED).

LpThreadIdjl - [out] Address of the DWORD variable that, on


success, receives the thread ID of the newly created thread

Thread Creation

Thread procedure

Use a function pointer


DWORD WINAPI ThreadProc( LPVOID
lpParameter );

Example of thread creation


DWORD WINAPI ThreadProc(LPVOID lpParameter){
printf("This is my windows thread\n");
}
int main(){
DWORD threadID;
CreateThread(NULL, 0, Thread1, NULL, 0, &threadID);
Sleep(1000); // Avoid too fast termination
return 0;
}

Thread Creation with Attributes


DWORD WINAPI Thread1(int param) {
printf("This is my thread n %i \n", param);
}
int main() {
DWORD threadID1;
DWORD threadID2;
DWORD threadID3;
DWORD threadID4;
CreateThread(NULL,
CreateThread(NULL,
CreateThread(NULL,
CreateThread(NULL,
Sleep(1000);
return 0;
}

0,
0,
0,
0,

Thread1,
Thread1,
Thread1,
Thread1,

1,
2,
3,
4,

0,
0,
0,
0,

&threadID1);
&threadID2);
&threadID3);
&threadID4);

Stopping/Resuming Threads
Function to stop the calling thread

VOID WINAPI ExitThread(


__in DWORD dwExitCode
);

However, must be used carefully:

The thread is exited before any destructors can be called or any other automatic
cleanup can be performed. Therefore, you should return from your thread function.

Function to resume a thread

DWORD WINAPI ResumeThread(


__in HANDLE hThread
);

Decrements a thread's suspend count. When the suspend count is decremented to


zero, the execution of the thread is resumed.

Function to suspend a thread


DWORD WINAPI ResumeThread(
__in HANDLE hThread
);

Thread Synchronization

How to synchronized our threads:


Events
Mutexes
Semaphores
Critical Sections
Condition Variables

Using Events

A thread wait for an event launched by another thread


Allow to remove the sleep call
Create an event :
HANDLE
__in
__in
__in
__in
);

CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
BOOL bManualReset, // reset type
BOOL bInitialState, // initial state
LPCTSTR lpName // object name

Wait for an event


DWORD WaitForSingleObject(
__in HANDLE hHandle, // handle to object
__in DWORD dwMilliseconds // time-out interval
);

Using Events

Signal event (becomes signaled):


BOOL SetEvent(
__in HANDLE hEvent // handle to event
);

Unsignal event:
BOOL ResetEvent(
__in HANDLE hEvent // handle to event
);

Others wait functions

WaitForSingleObject(), WaitForMultipleObjects(),
SignalObjectAndWait(), WaitForMultipleObjectsEx()
S
MsgWaitForMultipleObjects(), MsgWaitForMultipleObjectsEx,
MsgWaitForMultipleObjectsEx(), SignalObjectAndWait(),
WaitForMultipleObjectsEx(), WaitForSingleObjectEx()
W
R
RegisterWaitForSingleObject()

Using Events
HANDLE g_event;
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
printf("This is my thread\n");
SetEvent(g_event); // Once done set the event to
signaled
}
int main() {
g_event=CreateEvent(NULL, 0, 0, NULL); // Create the
event
DWORD threadID;
CreateThread(NULL, 0, Thread1, NULL, 0, &threadID);
WaitForSingleObject(g_event, INFINITE); // Wait for
the event
return 0;
}

Using Mutex Objects


Protect a shared resource from simultaneous
access by multiple threads or processes.

Each thread must wait for ownership of the mutex


before it can execute the code that accesses the shared
resource.

HANDLE WINAPI CreateMutex(


__in LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in_opt LPCTSTR lpName )

Getting the mutex by waiting for an event with


WaitForSingleObject

Releasing a mutex with


BOOL WINAPI ReleaseMutex(
__in HANDLE hMutex )

Using Mutex Objects

Main
// Create a mutex with no initial owner
ghMutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
);

In the thread procedure:


dwWaitResult = WaitForSingleObject(
ghMutex, // handle to mutex
INFINITE); // no time-out interval
// Do something
ReleaseMutex(ghMutex)

Using Semaphores

Very close to mutexes


ghSemaphore = CreateSemaphore(
NULL, // default security attributes
MAX_SEM_COUNT, // initial count
MAX_SEM_COUNT, // maximum count
NULL); // unnamed semaphore
dwWaitResult = WaitForSingleObject(
ghSemaphore, // handle to semaphore
INFINITE); // zero-second time-out interval
ReleaseSemaphore(
ghSemaphore, // handle to semaphore
1, // increase count by one
NULL) ) // not interested in previous count (out)

Using Critical Sections

Define a critical section by giving the entry


address (pointer)
InitializeCriticalSectionAndSpinCount(
&CriticalSection, //Pointer on the critical sections
0) // Spin count (0 on mono-processor)
...
// Release resources used by the critical section object.
DeleteCriticalSection(&CriticalSection)

Then call entry and exit functions


// Request ownership of the critical section.
EnterCriticalSection(&CriticalSection);
// Access the shared resource.
// Release ownership of the critical section.
LeaveCriticalSection(&CriticalSection);

Condition Variables

Same semantic as in Java or Pthreads


Four main functions
InitializeConditionVariable

Initializes a condition variable.

SleepConidtionVariableCS

Sleeps on the specified condition variable and releases the


specified critical section as an atomic operation.

SleepConditionVariableSRW

Sleeps on the specified condition variable and releases the


specified SRW lock as an atomic operation.

WakeAllConditionVariable

Wakes all threads waiting on the specified condition variable.

WakeConditionVariable

Wakes a single thread waiting on the specified condition variable.

Condition Variables
The producer/consumer example

EnterCriticalSection (&BufferLock);
while (QueueSize == BUFFER_SIZE && StopRequested == FALSE){
// Buffer is full - sleep so consumers can get items.
SleepConditionVariableCS (
&BufferNotFull, &BufferLock, INFINITE);
}
if (StopRequested == TRUE) {
LeaveCriticalSection (&BufferLock);
return; }
// Add the produced element
WakeConditionVariable (&BufferNotEmpty);
LeaveCriticalSection (&BufferLock);
}

Other Synchronization Mechanisms

Waitable timer objects


Waitable timers with an asynchronous procedure
call
One-time initialization
Using timer queues
Atomic Actions

Win32 Threads Issues

No garbage collection in C/C++ makes some


things more difficult
Complex to use

Do not forget to clean threads :

All threads need to be stopped nicely


All handles need to be closed

Conclusion

Threads are by default supported in Win32


However, it exists others library

MFC (Microsoft Foundation Class)


OpenThread (Close to Java thread)

To go further :

MSDN : http://msdn.com

Das könnte Ihnen auch gefallen