/* * File: thread.h * -------------- * This file exports a simple, platform-independent thread abstraction, * along with simple tools for concurrency control. */ /*************************************************************************/ /* Stanford Portable Library */ /* Copyright (c) 2014 by Eric Roberts <eroberts@cs.stanford.edu> */ /* */ /* This program is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation, either version 3 of the License, or */ /* (at your option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see <http://www.gnu.org/licenses/>. */ /*************************************************************************/ #ifndef _thread_h #define _thread_h #include <string>/* Forward definition */ class Lock;/* * Class: Thread * ------------- * This class encapsulates a lightweight process running in the same * address space as the creator. The class itself is opaque and is * manipulated by top-level functions as illustrated in the following * paradigm: * * Thread child = fork(fn); * ... code for the parent thread ... * join(child); * * This code calls fn so that it runs in parallel with the parent code. */ class Thread { public:/* * Constructor: Thread * Usage: Thread thread; * --------------------- * Creates an inactive thread variable that will typically be overwritten * by the result of a fork call. */ Thread();/* * Destructor: ~Thread * ------------------- * Frees any dynamic storage associated with the thread. */ virtual ~Thread();/* * Method: toString * Usage: string str = thread.toString(); * -------------------------------------- * Converts the thread to a string. */ std::string toString();/* Private section */ /**********************************************************************/ /* Note: Everything below this point in this class is logically part */ /* of the implementation and should not be of interest to clients. */ /**********************************************************************/ long id;/* id linking this thread to the platform-specific data */ };/* * Function: fork * Usage: Thread child = fork(fn); * Thread child = fork(fn, data); * ------------------------------------- * Creates a child thread that calls fn in an address space shared with the * current thread. The second form makes it possible to pass an argument * to fn, which may be of any type. */ Thread fork(void (*fn)()); template <typename ClientType> Thread fork(void (*fn)(ClientType & data), ClientType & data);/* * Function: join * Usage: join(thread); * -------------------- * Waits for the specified thread to finish before proceeding. */ void join(Thread & thread);/* * Function: yield * Usage: yield(); * --------------- * Yields the processor to allow another thread to run. */ void yield();/* * Function: getCurrentThread * Usage: Thread self = getCurrentThread(); * ---------------------------------------- * Returns the currently executing thread. */ Thread getCurrentThread();/* * Class: Lock * ----------- * This class represents a simple lock used to control concurrency. The * usual strategy for using locks is to use the synchronized macro * described later in this interface. */ class Lock { public:/* * Constructor: Lock * Usage: Lock lock; * ----------------- * Initializes a lock, which is initially in the unlocked state. */ Lock();/* * Destructor: ~Lock * ----------------- * Frees any heap storage associated with the lock. */ ~Lock();/* * Method: wait * Usage: lock.wait(); * ------------------- * Waits for some other thread to call signal on this lock. This call * requires that the lock be held by the calling thread. The effect of the * wait method is to release the lock and then wait until the desired * signal operation occurs, at which point the lock is reacquired and * control returns from the wait call. The wait method is typically used * inside a critical section containing a while loop to check for a * specific condition. The standard paradigm for using the waitThread * function looks like this: * * synchronized (lock) { * while (conditional test) { * lock.wait(); * } * ... code to manipulate the locked resource ... * } */ void wait();/* * Method: signal * Usage: lock.signal(); * --------------------- * Signals all threads waiting on the lock so that they wake up and recheck * the corresponding condition. */ void signal();/**********************************************************************/ /* Note: Everything below this point in this class is logically part */ /* of the implementation and should not be of interest to clients. */ /**********************************************************************/ private: long id;/* id linking this lock to the platform-specific data */ friend class Lock_State; };/* * Statement: synchronized * Usage: synchronized (lock) ... * ------------------------------ * Defines a critical section protected by the specified lock. The general * strategy for using this facility is shown in the following paradigmatic * pattern: * * synchronized (lock) { * ... statements in the critical section ... * } */ void lockForPlatform(int id); void unlockForPlatform(int id); class Lock_State { public: Lock_State(Lock & lock) { lp = &lock; finished = false; } bool advance() { if (finished) { unlockForPlatform(lp->id); return false; } else { finished = true; lockForPlatform(lp->id); return true; } } private: Lock *lp; bool finished; }; #define synchronized(lock) for (Lock_State ls(lock) ; ls.advance(); ) int forkForPlatform(void (*fn)(void *), void *dp); struct StartWithVoid { void (*fn)(); }; template <typename ClientType> struct StartWithClientData { void (*fn)(ClientType & data); ClientType *dp; }; template <typename ClientType> static void forkWithClientData(void *arg) { StartWithClientData<ClientType> *startup = (StartWithClientData<ClientType> *) arg; startup->fn(*startup->dp); } template <typename ClientType> Thread fork(void (*fn)(ClientType & data), ClientType & data) { StartWithClientData<ClientType> startup = { fn, &data }; Thread thread; thread.id = forkForPlatform(forkWithClientData<ClientType>, &startup); return thread; } #endif