0

Let's say we are building a threaded program in C, for example a message queue system where a thread is spawned to manage one end of the queue, and (re)connect sockets. This can roughly be set up in two ways (excuse the pseudocode):

void spawn_thrd(){
    struct S* = **allocate new structures for thread creature**
    int r = pthread_create(... , &thread_loop, S);
}
void destroy_thrd(pthread_t t){
    kill_thrd(t);
    join(t);
    free_structures(S);
}

or

void spawn_thrd(){
    int r = pthread_create(... , &thread_loop, S);
}
void destroy_thrd(pthread_t t){
    kill_thrd(t);
    join(t);
}
void thread_loop(){
    **allocate strucutures**
    while(1){
        **loop**
        **break on kill**
    }
    **free strucutures**
}

Namely, we can allocate the necessary structures for the functionality of the threaded operation outside the thread or inside it. As far as I can see, the only differences are that 1) in case the thread exits in an uncontrolled way, we have a memory leak (but this should be rare, or crash the entire program instead) and that 2) allocation in thread is a method less prone to race conditions. So is this a largely aesthetic choise?

3 Answers 3

1

I would do the latter, for several reasons.

First, the former requires passing data structures between threads, which requires coordination (synchronization) between them.  The latter would obviate that, which is a big win both in programmer effort and performance.  (We should not write code that mostly dodges race conditions.)

Second, very high performance threaded systems run into bottlenecks on multiprocessor systems, when the same memory locations are written by multiple threads (as would most likely necessary to allocate objects in one thread, use them in another, and release them in the other).  This has to do with the hardware MESI protocol, whereby a cache line has to be claimed for update in a process that resembles an expensive cache miss.

You would definitely want to have a for-sure way to handle deallocation in the case of forced terminations, if you need another thread to be able to kill one or more threads immediately.  (Otherwise, I'd install a top-level exception handler in that thread, if the programming language allows for that, to handle crashing.)

1

To me, the answer is neither this nor that - but both.

A thread should follow the same information hiding and locality guidelines as any other part of a proper program. Allocate the storage for the thread on "need-to-know" principles. The lifecycle of memory that's needed to transfer information in and out of the thread should be controlled from outside the thread, storage that is only relevant to the internal state of the thread should be controlled (that is, allocated, owned, and destroyed) from the inside.

If you sort your storage needs based on that concept, you minimize the risk of memory corruption and race conditions.

1
  • In particular, the thread probably has a "kill flag" to communicate that it should terminate. That needs to come from the outside. So should (IMO) a pointer to the queue it listens to. OTOH, anything the thread really needs for itself should be done inside the thread. Consider not even using the heap at all, but just put the thread info on the stack. Apr 22, 2021 at 9:18
0

Sometimes threads represent units of work, complete unto themselves. E.g., a webservice might (for some frameworks) get an incoming request and send it to a thread where that thread is dedicated to servicing that request until it gets an answer whereupon it sends the answer back as a response (then waits for its next assignment).

If your webservice framework is like this (or message passing framework, or whatever) then your thread is basically transaction based and all the memory it uses during that transaction can be free when the answer is returned as a response. In fact, it better be (otherwise you'd have a leak).

In cases like this you can use a region allocator: When a new request is handed to the thread a new region (memory pool) is created. From then on all heap memory needed for that transaction (on that thread) is allocated from that pool. When the request is returned (and copied by the framework to the network) then the region (pool) can be freed in its entirety.

This can give you a performance benefit - not having to free each piece of memory individually - as long as you can arrange that everything the thread needs comes from that one region.

This is just one example of where it is better for each thread to allocate its own memory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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