How to use Mutex in TBB

How to use Mutex in TBB

I would like to use Queuing Mutexin order to control the processing of some line of codes. On the top of code file, I defined queuing_mutex as CurrentMutexType like this:

typedef queuing_mutex CurrentMutexType;

  1. Can I instance from CurrentMutexType inside concurrent class built up to be used by parallel_for? And which type of modifier should I use (private mutable, public static, etc.)
  2. AfterI constructed lock and acquired lock on mutex using CurrentMutexType::scoped_lock(currentMutex) (I assume that currentMutex is an instance of CurrentMutexType), how do I destroy it inside operator function in concurrent class same as concurrent class in question 1, because I cannot use CurrentMutexType::scoped_lock::release() in non-static member function?
7 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Quoting - nuntawat

I would like to use Queuing Mutexin order to control the processing of some line of codes. On the top of code file, I defined queuing_mutex as CurrentMutexType like this:

typedef queuing_mutex CurrentMutexType;

  1. Can I instance from CurrentMutexType inside concurrent class built up to be used by parallel_for? And which type of modifier should I use (private mutable, public static, etc.)
  2. AfterI constructed lock and acquired lock on mutex using CurrentMutexType::scoped_lock(currentMutex) (I assume that currentMutex is an instance of CurrentMutexType), how do I destroy it inside operator function in concurrent class same as concurrent class in question 1, because I cannot use CurrentMutexType::scoped_lock::release() in non-static member function?

Hello,

it is good that you typedef'ed tbb::queuing_mutex as the CurrentMutexType. Queuing mutex preserves an order in which threads asked to acquire access and if your application changes over time and you will not need to preserve this order, you can just change the typedef ot tbb::spin_mutex and the rest of the code will just work.

To answer your questions:
To implement guarded access to a section of code using TBB one should create per-thread lock objects which in turn reference a global mutex object. One way to implement it would be to have a mutex as a static member and in your class operator(), when a thread needs to acquire a lock on a mutex, you create a lock object. Lock object usually is an auto variable and mutex is acquired in it's constructor and released in the destructor, when the lock object goes out of scope (hence the name for the sub-type "scoped_lock").

The code bellow should give an idea how this can be implemented. An access to std::cout is guarded in this example, because it is not safe to write to the standard output from parallel threads. An important note here is allthough it is a goodexample of HOW to use synchronization primitives andon the outside itlooks like a parallel application, which uses TBB, in reality this is a serial application (since all the code insideoperator() of the function object is inside a critical section)+ TBB scheduling overhead + synchronization overhead. This note is to remind you that if revising your algorithm is possible, so that the implementation will not have to use mutexes, it will be much more beneficial to change the algorithm first.

#include 
using namespace std;

#include "tbb/task_scheduler_init.h"
#include "tbb/blocked_range.h"
#include "tbb/parallel_sort.h"
#include "tbb/queuing_mutex.h"

typedef tbb::queuing_mutex currentMutex_t;

class A {
public:
    static currentMutex_t mutex;

    void operator() (const tbb::blocked_range& r) const {
        for(size_t i = r.begin(); i < r.end(); ++i) {
            currentMutex_t::scoped_lock lock(mutex);
            cout << i << " ";
        }
    }
};

currentMutex_t A::mutex = currentMutex_t();

int main(int argc, char* argv[]) {
    tbb::task_scheduler_init init;

    tbb::parallel_for(tbb::blocked_range(0, 20), A(), tbb::auto_partitioner());

    return 0;
}

Thanks for your solution. I tested your code and result was passed, but when

1. I configured on the project properties page completely:

Configuration Properties section:
- Debugging | Environment: PATH=$(TBB21_INSTALL_DIR)ia32vc9bin;$(PATH)
- C/C++ | General | Additional Include Directories: $(TBB21_INSTALL_DIR)include
- Linker | General | Additional Library Directories: $(TBB21_INSTALL_DIR)ia32vc9lib
- Linker | Input | Additional Dependencies: tbb_debug.lib
- Build Events | Post-Build Event | Command Line: copy "$(TBB21_INSTALL_DIR)ia32vc9bintbb_debug.dll" "$(OutDir)"

2. I adopt your solution into my code like this:

// Assume that I imported tbb abd related libraries.

using namespace tbb;

typedef queuing_mutex CurrentMutexType;

class ParallizingHT {
private:
  // A lot of private variables.

public:
  static CurrentMutexType currentMutex;

  void operator() (const block_range& range) const {
    // More codes go here.
  }

  // Constructor goes here.
};

And then I compiled it, Visual Studio 2008 shown error code LNK2001: unresolved external symbol "public: static class tbb::queuing_mutex ParallizingHT::currentMutex" (?currentMutex@ParallizingHT@@2Vqueuing_mutex@tbb@@A) .obj

What should I do? (I use TBB 2.1 that is the part of Intel Parallel Studio Beta Update 2)

Quoting - nuntawat

And then I compiled it, Visual Studio 2008 shown error code LNK2001: unresolved external symbol "public: static class tbb::queuing_mutex ParallizingHT::currentMutex" (?currentMutex@ParallizingHT@@2Vqueuing_mutex@tbb@@A) .obj


What should I do? (I use TBB 2.1 that is the part of Intel Parallel Studio Beta Update 2)

Hello,

that linker error means that you forgot to initialize the mutex, which you declared as a static variable in the scope of your function object class. This is what line 23 in my original listing refers to.

Oh! I forgot it!! Thanks very much for you notice, butI have another question about ReaderWriterMutex, especially for queuing_rw_mutex. I have some codes that must be run by serial, and anothers can be run by parallel, so I would like to use upgrade_to_writer and downgrade_to_reader functions alternativelyin the operator method of class used by parallel_for function. When I compiled, I have got the error message which tell that upgrade_to_writer or downgrade_to_reader are illegal called of non-static member function. What should I do?

Quoting - nuntawat

Oh! I forgot it!! Thanks very much for you notice, butI have another question about ReaderWriterMutex, especially for queuing_rw_mutex. I have some codes that must be run by serial, and anothers can be run by parallel, so I would like to use upgrade_to_writer and downgrade_to_reader functions alternativelyin the operator method of class used by parallel_for function. When I compiled, I have got the error message which tell that upgrade_to_writer or downgrade_to_reader are illegal called of non-static member function. What should I do?

Hello, from the looks of compiler error it seems that you're trying to call a non-static member function (upgrade_to_writer()) for queuing_rw_mutex class, but not for the lock object. The following code compiles and works fine.

class A {
public:
    static currentMutex_t mutex;

    void operator() (const tbb::blocked_range& r) const {
        for(size_t i = r.begin(); i < r.end(); ++i) {
            currentMutex_t::scoped_lock lock(mutex, false);
            lock.upgrade_to_writer();
            cout << i << " ";
        }
    }
};

One thing I also wanted to mention here is that reader-writer mutexes generally are beneficial, when there are a lot more readers than writers. Recent publications on the subject (not talking about TBB in particular) claim that if there are more than 10% of writers present, the code will run slower than it would have if it just used plain user-level mutex without reader-writer support. Maybe a good idea would be to micro-benchmark the scenario you're implementing in your code before settling for RW mutex.

Quoting - Anton Pegushin (Intel)

Hello, from the looks of compiler error it seems that you're trying to call a non-static member function (upgrade_to_writer()) for queuing_rw_mutex class, but not for the lock object. The following code compiles and works fine.

class A {
public:
    static currentMutex_t mutex;

    void operator() (const tbb::blocked_range& r) const {
        for(size_t i = r.begin(); i < r.end(); ++i) {
            currentMutex_t::scoped_lock lock(mutex, false);
            lock.upgrade_to_writer();
            cout << i << " ";
        }
    }
};

One thing I also wanted to mention here is that reader-writer mutexes generally are beneficial, when there are a lot more readers than writers. Recent publications on the subject (not talking about TBB in particular) claim that if there are more than 10% of writers present, the code will run slower than it would have if it just used plain user-level mutex without reader-writer support. Maybe a good idea would be to micro-benchmark the scenario you're implementing in your code before settling for RW mutex.

Oh! I has just known that I can use lock object to do the same operation as static method can do! Well, I'll trade off between queuing_rw_mutex and queuing_mutex. Thanks a lot again Aoton!

Leave a Comment

Please sign in to add a comment. Not a member? Join today