October 25, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

For Brew Developers, There Are New Kids in Town: IThread & IRscPool

  • September 30, 2003
  • By Radu Braniste
  • Send Email »
  • More Articles »

Please note that this article covers features that is not officially supported BREW 2.1 and earlier. - Developer.com

Abstract:

IThread is a new interface added in BREW 2.1, conferring BREW cooperatively scheduled multithreading support. IThread allows writing code with blocking semantics in an otherwise non-blocking world. Another interesting interface was released in the same SDK: IRscPool—an interface capable of controlling the lifetime of associated resources. The first part of this article will present these two interfaces and analyze some related usage patterns and their limitations. The next installment will present a possible solution to these limitations.

IRscPool and IThread—a Short Presentation

IRscPool is intended as an IBase and memory-based anonymous resources repository. Once a resource was added to the pool, its life is inextricably linked to the life of the pool—and is automatically deallocated (in a specific way, depending of its type) once the pool is released.

IRscPool offers these specific methods:

int IRSCPOOL_HoldRsc(IRscPool *p, 
                     IBase *pResource);
// associates a IBase resource with 
// an IRscPool
uint32 IRSCPOOL_ReleaseRsc(IRscPool *p, 
                           IBase *pResource);
//releases the IBase resource
void *IRSCPOOL_Malloc(IRscPool *p, 
                      unsigned uSize);
// allocates memory and associates it 
// with an IRscPool
void IRSCPOOL_Free(IRscPool *p, void *p);
// frees the memory previously associated 
// with an IRscPool

Short example:

IRscPool* pool_;
R* runnable_;
boolean CPPApp::OnAppInitData()
{
  pool_=0;
  int err = ISHELL_CreateInstance(m_pIShell, 
                                  AEECLSID_RSCPOOL,
                                  (void**)(&pool_));
  IRSCPOOL_HoldRsc(pool_,(IBase*)thread);
//placement new
  void* v = IRSCPOOL_Malloc(pool_,sizeof(R));
runnable_ = new(v) R(shell_);
  return TRUE;
}

void CPPApp::OnAppfreeData()
{
  if (pool_)
  IRSCPOOL_Release(pool_);
}

A pool is created and an IThread is associated (RSCPOOL_HoldRsc). A void* v is (m)allocated and associated with the pool. IRSCPOOL_Release deallocates both v and thread. Please note that thread and v don't have to be known at a global scope—pool_ is the only well-known, global repository.

Some comments from the API are worthy noticing:

"Comments: IRscPool does not _AddRef() the IBASE passed in, so don't _Release() it after calling _HoldRsc()."

Before moving to IThread, please note that IRscPool is just a "memory arena" and has no intrinsic way of enumerating its elements.

IThread

IThread is defined as an "interface definition for a cooperatively scheduled threads implementation." IThread extends IRscPool, so it is possible to explicitly associate resources with the lifetime of a thread.

IThread specific methods:

int ITHREAD_Start(IThread *p, int nStackSz, 
                  PFNTHREAD pfStart, void *pvStart);
where 
typedef int (*PFNTHREAD)(IThread *piThread, void *pvCxt); 

int ITHREAD_Exit(IThread *p, int nRv);
int ITHREAD_Stop(IThread *p, int nRv);
void ITHREAD_Join(IThread *p, AEECallback *pcb, int *pnRv);
void ITHREAD_Suspend(IThread *p);

There is also a ThreadUtil library—defined in AEEThreadUtil.h—that extends IThread functionality. This is implemented exclusively by using IThread public methods and the source code is available in src/thrdutil. The library implements some of the thread functionality usually found in thread libraries—mutexes, condition variables, local storage, and so forth—and additionally demonstrates blocking network operation. Of course, we don't have to forget that locks make little sense for cooperatively-scheduled threads, but synchronization is still very important.

The central method in IThread is Suspend; it is what confers the blocking character to IThread. For example, let's take a look at the threadUtil "sleep" implementation:

void IThread_Sleep(IThread *me,IShell *piShell, int nMS)
{
   AEECallback *pcb = ITHREAD_GetResumeCBK(me);
   ISHELL_SetTimerEx(piShell,nMS,pcb);
   ITHREAD_Suspend(me);    //execution stops here for nMs msec
//and resumes here
}

Every thread has its own stack, allocated at creation time. The size has to be explicitly specified: "nStackSz should be specified as low as possible to accomodate the IThread's start function and its user-level descendants. Additional stack bytes will be allocated for system calls." Please note that there is presently no functionality to get the minimal stack size (thr_min_stack() in some implementations1) or the possibility to create a stack with a default, library/OS dependent size. (Common thread packages discourage the use of a user-supplied stack in the first place.)

Using IThread and IRscPool

Using IRscPool from C is straightforward. Basically, if a resource is a descendant of IBase, HoldRsc/ReleasRsc will be used; if not, Malloc/Free will be used.

C++ usage is far less obvious. Let's assume we have a class R and

R* runnable_;

has to be associated with IRscPool pool_. Because R is not derived from IBase and the instance has to be constructed using the class constructor, we must instantiate runnable_ using IRSCPOOL_Malloc and use placement new, like this:

void* v   = IRSCPOOL_Malloc(rsc,sizeof(R));
runnable_ = new(v) R(shell_);

with placement new defined like so:

void* operator new(size_t, void* location)
  {
    return location;
  }

For a discussion about the dangers of using placement new (especially alignment issues), see this example: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10.

Stroustrup discusses about placement new and why there is no placement delete in this article: http://www.research.att.com/~bs/bs_faq2.html#placement-delete.

What's important in this discussion is that the destructor has to be called explicitly—see http://www.embedded.com/1999/9901/9901feat2.htm.

This means that we have to pair every:

void* v   = IRSCPOOL_Malloc(pool_,sizeof(R));
runnable_ = new(v) R(shell_);

with

  runnable_->~R();
  IRSCPOOL_Free(pool_,runnable_);

for an explicit release.

The automatic destruction of the "memory arena" is again of little help. We still have to call

runnable_->~R();

runnable_ has to be known at a global scope—defying the whole purpose of IRscPool. Of course, using classes with default destructors is perfectly plausible but extremely restrictive.

Using IThread from C is straightforward. A simple scenario involves thread creation, data allocation in TLS (TLS uses internally ITHREAD_Malloc), and thread destruction, together with the associated data, once PFNTHREAD exits. Basically, the attached examples can be "unrolled" and immediately used in C.

An additional problem here is asynchronous cancellation. This is known to be the Achilles' heel of preemptive multithreading (see 2 for a complete discussion, 3 mentioning: "Asynchronous cancellation is dangerous and its use should probably be restricted to tight computation loops in which the overhead of calling pthread_testcancel() would be severe"). The Boost.Thread library, (http://www.boost.org/libs/thread/doc/index.html) as well as ACE (http://www.cs.wustl.edu/~schmidt/ACE.html), doesn't address this problem at all. Even cooperative implementations might be affected and, from a C++ perspective, this is unavoidable. Thread cancellation is difficult to do correctly in a C++ program because you want to run all the destructors of stack objects of the thread being canceled. Automatic C++ object destructors and C++ exception handling are in direct conflict with the "philosophy" of the IThread cancellation programming paradigm. For a more thorough explanation, let's consider the following PFNTHREAD implementation:

void operator()(BThread<RunnableImpl>* t)
{
  Writer writer(shell_);
  while (cursor_ < 3)
  {
    String str((long)cursor_);
    writer.DisplayOutput(1,str.toCharArray());
    t->sleep(30000);
    int i = cursor_++;
  }
}

str and writer are automatic objects with nontrivial destructors. Interrupting sleep() asynchronously is equivalent with abandoning PFNTHREAD in the midst of the while execution—and never unwinding the stack and giving str and writer a chance to run their destructors.

Let's stop for a minute and analyze what we can do with IThread and IRscPool in C++. Threads are classic examples of the RAII—Resource acquisition is initialization—idiom so that it makes a lot of sense to wrap them in a BThread class (see Bthread.h). A BThread has to know its PFNTHREAD function as well as the stack size—a good candidate for a policy. I didn't expose other thread specific functionality than sleep. We'll do more in the next part of our series.

template <class R, class P = BThreadPolicy>
class BThread
{
public:
  ~BThread()
  {}

  static BThread* createThread(IShell* shell, IRscPool *rsc);

  int start()
  {
    return ITHREAD_Start(thread_, P::getStackSize(), runImpl, this);
  }
  void sleep(int ms);    //the only thread specific functionality
                         //implemented
private:
  ... ...
  static int runImpl(IThread* t, void* v)
  {
    BThread* r = static_cast<BThread*>(v);
    (*(r->runnable_))(r);
    return SUCCESS;
  }
... ...
R* runnable_;
};

PFNTHREAD has to be adapted to C++—and implementing it as a functor offers lot of flexibility:

struct RunnableImpl
{
  void operator()(BThread<RunnableImpl>* t);
};

The above implementation can be used like this:

  typedef BThread<RunnableImpl> BThr;
  BThr *t = BThr::createThread(m_pIShell,pool_);
  int err = t->start();

Internally, IThread is added to an IRscPool and the functor is added to the thread's pool:

int allocate()
  {
    int err   = SUCCESS;
    if ((err  = ISHELL_CreateInstance(shell_, AEECLSID_THREAD,
        (void**)(&thread_))) != SUCCESS)
      return err;
    err       = IRSCPOOL_HoldRsc(pool_,(IBase*)thread_);
    void* v   = ITHREAD_Malloc(thread_,sizeof(R));
    runnable_ = new(v) R(shell_);
    if (!runnable_)
      return 1;
    return err;
  }

This arrangement guarantees that:

  • The functor lives as long as the thread lives.
  • The thread deallocation is done automatically when the RscPool is released.

IRscPool is the only object that has to be known at a "global" scope. As long as ~BThread and ~RunnableImpl are default constructors everything is OK from the point of view of memory arenas.

void operator()(BThread<RunnableImpl>* t)
  {
    Writer writer (shell_);
    while (cursor_ < 30)
    {
      String e ((long)cursor_);
      writer.DisplayOutput(1,e.toCharArray());
      t->sleep(100);
      int i = cursor_++;
    }
  }

This operator()() implementation is safe if no asynchronous cancellation takes place. As explained before, in that case, the stack is not unwound and the automatic destruction cannot take place. This means that all we can use are POD structures and/or objects known at the global scope, for example (RunnableImpl1):

void operator()(BThread<RunnableImpl1>* t)
  {
    while (cursor_ < 30)
    {
      writer_->WriteLine(cursor_);
      t->sleep(100);
      int i = cursor_++;
    }
  }

where writer_ is an object known at the global scope. This is far from being a desirable solution.

A Note about Compilers

The attached code was compiled using VC7.1. It can be ported to be used with other compilers as described in previous articles.

Next Time

We will make an analysis of the current solution and present a better approach—flexible and with no limitations.

End Notes

1. Solaris. Multithreaded Programming Guide, Prentice Hall, 1995

2. Douglas Lea, Concurrent Programming in Java(TM): Design Principles and Pattern, Addison-Wesley 1999

3. David R. Butenhof, Programming with POSIX Threads, Addison-Wesley 1997

About the Author

Radu Braniste is Director of Technology at Epicad. He can be contacted at rbraniste@epicad.com

# # #






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel