8

this is my first attempt at threading in C. I am creating a circularly bounded buffer. I know how to create the thread, but all examples I have seen only have threaded functions that accept one void parameter, but unfortunately my worker's specification requires me to use three, as shown here:

void bufferRead(BoundedBuffer* buffer, char* data, int count) {
    pthread_mutex_lock(&buffer->mutexBuffer);
    <snip>
    pthread_mutex_unlock(&buffer->mutexBuffer);
}

Here is my pthread_create statement

pthread_create(&buffer.readThread, NULL, (void *)bufferRead, &readParams)

And my readParams struct/assignments

struct readThreadParams {                                                   
    BoundedBuffer b;                                                        
    char* data;                                                             
    int count;                                                              
};                                                                          

struct readThreadParams readParams;                                         
readParams.b = buffer2;                                                     
readParams.data = out_array;                                                
readParams.count = in_size;

Any advice on how to assign each of the struct's parameters after passing to the bufferRead function would be greatly appreciated.

4 Answers 4

15

That's because you only really need one parameter. When we have more than one value, as is typically the case, we encapsulate that into a struct. The function type that pthread_create will call is non-negotiable. This is an area where type-casting your function pointer can get you into serious trouble.

#include <pthread.h>
#include <stdlib.h>

struct BoundedBuffer {
    pthread_t readThread;
    pthread_mutex_t mutexBuffer;
} buffer2;

struct readThreadParams {
    struct BoundedBuffer b;
    char* data;
    int count;
};

void *bufferRead (void *context) {
    struct readThreadParams *readParams = context;

    pthread_mutex_lock(&readParams->b.mutexBuffer);
    //<snip>
    pthread_mutex_unlock(&readParams->b.mutexBuffer);

    return NULL;
}

int main(void) {
    int ret;
    char *out_array = malloc(42);
    size_t in_size = 42;

    struct readThreadParams readParams;
    readParams.b = buffer2;
    readParams.data = out_array;
    readParams.count = in_size;

    /* I presume that by "buffer", you really meant the .b member of
     * struct readThreadParams.  Further, this must have a member
     * named readThread of type pthread_t, etc.
     */
    ret = pthread_create(&readParams.b.readThread, NULL, bufferRead, &readParams);

    if (!ret) {
        pthread_join(&readParams.b.readThread, NULL);
    }

    free(out_array);

    return ret;
}
Sign up to request clarification or add additional context in comments.

7 Comments

If it's a local variable in main that's not a problem. If you exit main then your program terminates anyway so the variable always exists while your program is running.
@jcoder Leaving main doesn't mean immediate program termination. And automatic (non-heap, non-static) variable for inter-thread communication is a questionable idea at best.
Ok you are correct, there is a tiny interval between leaving main and performing exit. So it's not safe. I agree. I was going to write something about how as long as you create your thread object after the local variable you pass in you are guarenteed to be safe, but this is c/pthreads, not c++ as I first thought.
The OP code makes no mention of main(). Using a local stack struct as a parameter is very dangerous in the general case of non-trivial apps.
@Valeri Atamaniouk the point of this exercise was to demonstrate the basic mechanism for creating a thread. You'll notice I had a comment about joining the thread before you exit, which would prevent that problem. I've gone ahead and replaced that with the code to actually join. Passing data on a stack to a thread is not in-and-of its self a bad thing -- it depends upon the context. In this case, I don't care that my main thread has to block until the worker thread is done.
|
4

Start function has to take argument. So your direction is right:

struct readThreadParams {                                                   
    BoundedBuffer *b; 
    char *data;                                                             
    int count;                                                              
};     

Then, you need to allocate the variable on heap, not on stack:

struct readThreadParams *readParams;

readParams = malloc(sizeof(*readParams));
readParams->b = buffer2;                                                     
readParams->data = out_array;                                                
readParams->count = in_size;

After which you can give it createThread:

pthread_create(&buffer.readThread, NULL, bufferRead, readParams);

Thread function shall take only 1 argument (void*):

void *bufferRead(void *arg)
{
    struct readThreadParams *params = arg;
    BoundedBuffer *buffer = params->b;
    char* data = params->data;
    int count = params->count;

    pthread_mutex_lock(&buffer->mutexBuffer);
    <snip>
    pthread_mutex_unlock(&buffer->mutexBuffer);

    return NULL;
}

Comments

3

You got in the right way.

The function prototype should be like

void*  bufferRead(void *arg)
{
   ....
}

And typecast the argument to required type in thread function. Here, it should be

void*  bufferRead(void *arg)
{
     struct readThreadParams *input = (struct readThreadParams*)arg;
}

Passing more than one arguments to pthread function is not possible directly. so mostly formed as structure and passed to the function.

Refer this tutorial for more details on pthreads.

5 Comments

Thanks for the quick reply! Just wondering, but is there any way to still use the function prototype I currently have?
you can typecast and create. But no use. so void * ()(void*) is recommended.
@ChrisDevWard No, you cannot. Well, the only way to use that prototype is to call it directly from your void *start_routine(void *) function that the pthread library will call, but that would needlessly introduce the overhead of another function call.
@Jeyaram Also, the typecast from void* to another pointer type isn't strictly required (I'm not sure about what C standards this may be required in), but going from another pointer type to a void* does require an explicit type cast.
@DanielSantos A typecast from void * can be converted to any pointer to object type, and back again, and the value shall convert equal. This is in all C standards, AFAIK. Aspects of undefined behaviour exists if you attempt to access the object that it points to, and the pointer isn't aligned suitably to store that type, or if it doesn't point to an object of a suitable size or representation.
0

This example borders on preprocessor abuse, but I like it because it demonstrates mimicking default argument values.

#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef char *BoundedBuffer;

struct read_thread_param {  
    pthread_t thread;                                                 
    BoundedBuffer buffer;                                                        
    char* data;                                                             
    int count;                                                              
}; 

void buffer_read(BoundedBuffer* buffer, char* data, int count) {
    pthread_mutex_lock(&buffer->mutexBuffer);
    /*snip*/
    pthread_mutex_unlock(&buffer->mutexBuffer);
}

void *buffer_read_entrance(void *object) {
    struct read_thread_param *param = object;
    if (param->thread != 0) {
        buffer_read(&param->buffer, param->data, param->count);
        free(param);
        return NULL;
    }

    param = malloc(sizeof *param);

    /* TODO: Handle allocation error */
    assert(param != NULL);

    memcpy(param, object, sizeof *param);

    /* TODO: Handle thread creation error */
    assert(pthread_create(&param->thread, NULL, buffer_read_entrance, param) == 0);
    return NULL;
}

#define buffer_read_entrance(...) buffer_read_entrance(&(struct read_thread_param) { .thread = 0, __VA_ARGS__ })
void buffer_read(BoundedBuffer* buffer, char* data, int count);

int main(void) {
    buffer_read_entrance(.buffer = "hello world", .count = 42);
}

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.