1
votes

I am confused how to use Mutex using POSIX. Consider the following code:

void *print_message_function( void *ptr );
pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
main()
{
     pthread_t thread1, thread2,thread3;
     std::string message1 = "Apple";
     std::string message2 = "Orange";
     int  iret1, iret2,iret3;


     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) &message1);
     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) &message1);
     iret3 = pthread_create( &thread3, NULL, print_message_function, (void*) &message2);

     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 
     pthread_join( thread3, NULL); 

     exit(EXIT_SUCCESS);
}

void *print_message_function( void *ptr )
{
    pthread_mutex_lock( &count_mutex );
    int i = 3 ;
    while( i ) { 

        printf("i is %d ...%s \n", i,(*(std::string *)ptr).c_str() );
        i-- ;
        sleep (1) ;
    }
    pthread_mutex_unlock( &count_mutex );
}

Thread1 and thread 2 use a common resource -- message1. Thread3 uses own resource -- message 3. Messing up printing on STDOUT is OK for me.

The output of the program is

i is 3 ...Apple
i is 2 ...Apple
i is 1 ...Apple
i is 3 ...Apple
i is 2 ...Apple
i is 1 ...Apple
i is 3 ...Orange
i is 2 ...Orange
i is 1 ...Orange

As we see thread3 is executed at the end. Since thread3 doesn't use any common resource, how can I make it "skip the mutex lock". How can I make a mutex lock enable only when two threads are accessing a common piece of memory and not otherwise. Mutex must be dynamic. I am open to even specifying a variable which shouldn't point to same location in memory for the lock to be activated. In other words, how can I make a mutex that gets activated only when two threads collide.

I understand this might be a duplicate question. But I was unable to find any solution for this problem. Also, due to some environment restrictions, I cant use C++11 STL threads or boost. I would like help in pthreads library.

3

3 Answers

2
votes

Right now your problem is that you are doing mutex_lock on the same object. Would something like this work?

struct DataWithMutex {
   std::string message;
   pthread_mutex_t count_mutex;
};

void *print_message_function( void *ptr );
main()
{
     pthread_t thread1, thread2,thread3;

     DataWithMutex data1 = {"Apple", PTHREAD_MUTEX_INITIALIZER};
     DataWithMutex data2 = {"Orange", PTHREAD_MUTEX_INITIALIZER};
     int  iret1, iret2,iret3;


     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) &data1);
     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) &data1);
     iret3 = pthread_create( &thread3, NULL, print_message_function, (void*) &data2);

     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 
     pthread_join( thread3, NULL); 

     exit(EXIT_SUCCESS);
}

void *print_message_function( void *ptr )
{
    DataWithMutex* const data = (DataWithMutex*)ptr;
    pthread_mutex_lock( &(data->count_mutex) );
    int i = 3 ;
    while( i ) { 

        printf("i is %d ...%s \n", i,data->message.c_str() );
        i-- ;
        sleep (1) ;
    }
    pthread_mutex_unlock( &(data->count_mutex) );
}
1
votes

I would say that if you're holding a mutex for long enough that this is a problem, you're probably doing something wrong in your code. Just reduce the time the mutex is held to the absolute minimum and in examples like your code above, the problem will be resolved.

0
votes

You mention that you can't use C++11, and you're using std::string, so I'm going to assume that you can use C++03.

Code:

One solution is something along these lines:

(Live, compilable & runnable example: http://coliru.stacked-crooked.com/a/376cfc4b50405333)

#include <iostream>
#include <pthread.h>
#include <unistd.h>

class LockGuard {
    public:
        LockGuard(pthread_mutex_t& mutex)
          : m(mutex)
        {
            pthread_mutex_lock(&m);
        }

        ~LockGuard()
        {
            pthread_mutex_unlock(&m);
        }
    private:
        pthread_mutex_t& m;
};

class Access {
    public:
        Access(std::string& message)
          : msg(message) {}

        virtual std::string read() const
        {
            return msg;
        }

        virtual void write(const std::string& new_msg)
        {
            msg = new_msg;
        }

    protected:
        std::string& msg;
};

class LockedAccess : public Access {
    public:
        LockedAccess(std::string& message)
          : Access(message)
        {
            pthread_mutex_init(&mutex, NULL);
        }

        std::string read() const
        {
            LockGuard lock(mutex);
            return msg;
        }

        void write(const std::string& new_msg)
        {
            LockGuard lock(mutex);
            msg = new_msg;
        }

    private:
        mutable pthread_mutex_t mutex;
};

void* print_message_function(void* ptr)
{
    Access* accessor = static_cast<Access*>(ptr);
    int i = 3;
    while (i)
    {
        printf("i is %d ... %s \n", i, accessor->read().c_str());
        i--;
        sleep(1);
    }
    return NULL;
}

int main()
{
    std::string message1 = "Apple";
    std::string message2 = "Orange";
    Access unlocked_access(message2);
    LockedAccess locked_access(message1);

    pthread_t thread1, thread2, thread3;
    int iret1, iret2, iret3;

    iret1 = pthread_create(&thread1, NULL, print_message_function, static_cast<void*>(&locked_access));
    iret2 = pthread_create(&thread2, NULL, print_message_function, static_cast<void*>(&locked_access));
    iret3 = pthread_create(&thread3, NULL, print_message_function, static_cast<void*>(&unlocked_access));

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);

    return 0;
}

Explanation:

Here, we take advantage of:

  1. RAII (or scope-based resource management) to ensure the locking is correct, and unlocking occurs on function exit, even if that's via an exception

  2. Virtual dispatch to do different things depending on the type we were provided with.

The idea is to provide a class interface for accessing the message, and provide a derived class which presents the same interface but does something different under the hood - it takes locks. Your thread function can now continue to call the function read(), and virtual dispatch will determine whether the locked or unlocked version is called.

You still have to decide whether the resource is shared, and wrap it in an appropriate Access class. There really isn't a way around this; the compiler, and system in general, does not know in advance that something is only accessed by one thread. If you can't decide this in advance, the best you can do is provide a mutex per message, so that the locks are independent (i.e. thread 3 will have no contention on the lock for the "Oranges" message, but will still have to lock it; threads 1 and 2 will contend on the lock for the "Apples" message).

In C:

If you cannot, in fact, use C++, you can achieve something broadly similar in C by having a lock per message / piece of potentially shared data. You could bundle the data and the lock together into a struct, and pass a pointer to that struct into your thread. The thread must then take the lock before accessing the other member of the struct.

Locking Granularity:

As a final note, you should try to determine a reasonable locking pattern for your particular program; taking locks can be expensive, especially when there is contention. It is typical to refer to the lock granularity, which refers to how much of the code is covered by a lock. In your example, the entire thread function is covered by a lock. In my code above, only the access to the shared variable is covered. This allows the contending threads to make more progress, at the cost of taking and releasing the lock more often. The pattern you require will depend on your application, and you may need to spend some time thinking about the best approach.