4
\$\begingroup\$

I want a homegrown version of C++11's std::thread. My motivation is to avoid manual memory management of the arguments passed to the thread function. I'm restricted to C++98. I want better type safety than what's available in the POSIX and Win32 thread APIs.

Two notes:

  1. I can't think of a way to avoid a heap allocation for the thunk argument. The thread instance could be deleted before the thunk has a chance to construct its arguments. I suppose I could wait on an event in the constructor signaled by thunk() once the thread is in a 'ready' state, but that seems horrible.
  2. I'm not too concerned about exception safety as exceptions are disabled for most of this C++ codebase.

/* There is a portable thread implementation in C available already */
int portable_thread_create(portable_thread_t *)
{
    pthread_create() **OR**
    _beginthreadex()
}

#define PORTABLE_THREAD_FUNC_DECL void *
#define PORTABLE_THREAD_FUNC_DECL unsigned int __cdecl **OR**

void portable_thread_join(portable_thread_t)
{
    pthread_join() **OR**
    WaitForSingleObject()
}

template < typename TArg0 >
class thread
{
public:
    typedef void (*function_t)(TArg0);
    thread(function_t func, TArg0 arg0)
        : m_func(func)
    {
        m_arg0 = new TThunkArg(this, arg0);
        if (0 != portable_thread_create(&m_thread, thunk, m_arg0))
        {
            delete m_arg0;
            throw new std::runtime_error("Could not create a thread");
        }
    }

    void join()
    {
        portable_thread_join(m_thread);
    }

private:
    struct TThunkArg
    {
        thread<TArg0> *Self;
        TArg0 Arg0;
        TThunkArg(thread<TArg0> *self, TArg0 arg0)
            : Self(self)
            , Arg0(arg0)
        {}
    };
    static PORTABLE_THREAD_FUNC_DECL thunk(void *unsafe_args)
    {
        TThunkArg *args = reinterpret_cast<TThunkArg*>(unsafe_args);
        try
        {
            args->Self->m_func(args->Arg0);
        }
        catch(...)
        {
            delete args;
            throw;
        }

        delete args;
        return 0;
    }
    portable_thread_t m_thread;
    function_t m_func;
    TThunkArg *m_arg0;
};
\$\endgroup\$
  • 3
    \$\begingroup\$ Aside: can you use boost? They provided a threading library before it got in the standard. \$\endgroup\$ – user14393 Apr 25 '15 at 6:35
  • 1
    \$\begingroup\$ No Boost for this codebase. \$\endgroup\$ – Cat Zimmermann Apr 25 '15 at 7:39
4
\$\begingroup\$

You can put the unsafe_args in a auto_ptr so RAII can delete them safely. This is safe because you will not be copying it around.

static PORTABLE_THREAD_FUNC_DECL thunk(void *unsafe_args)
{
    std::auto_ptr<TThunkArg> args(reinterpret_cast<TThunkArg*>(unsafe_args));
    args->Self->m_func(args->Arg0);

    return 0;
}

Also don't worry about having to new the args; you are creating a new thread, this is much more expensive than a quick malloc call.

\$\endgroup\$
3
\$\begingroup\$
  • I don't see a reason not to make portable_thread_create and portable_thread_join static members of thread. If they are not meant for use outside the class, also make them private.

  • _beginthreadex, pthread_create and CreateThread all expect a pointer to a C function. Technically, a C function is not the same as a static class member. Most sane compilers will treat them the same way, but for optimal portability, you should define you thunk function as a global extern "C":

    extern "C" PORTABLE_THREAD_FUNC_DECL thunk(void *unsafe_args) { /* ... */ }
    
  • std::auto_ptr, even though deficient if compared to its C++11 replacements, is still cleaner and safer than raw new/delete in most scenarios.

  • thread currently has no destructor, so it is not cleaning up the platform specific thread handle. I think you have a resource leak there. Also, it does not join or detach the thread on destruction. Even though this is how the standard C++ thread behaves, I would advise that you perform a default action in the destructor instead of leaving room for undefined behavior.

\$\endgroup\$
  • 1
    \$\begingroup\$ about point 2: or add extern "C" to the PORTABLE_THREAD_FUNC_DECL macro \$\endgroup\$ – ratchet freak Apr 25 '15 at 4:10
  • \$\begingroup\$ The portable_thread functions are C functions from elsewhere. Most of the C code is elided, but it's safe to say PORTABLE_THREAD_FUNC_DECL contains the extern "C" and other calling conventions for other platforms. portable_thread_detach is definitely needed, but I think join in a destructor might be confusing. \$\endgroup\$ – Cat Zimmermann Apr 25 '15 at 4:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.