shared_ptr thr-safety: confusions %%take

At least 2 interviewers pointed out thread safety issues …

http://stackoverflow.com/questions/14482830/stdshared-ptr-thread-safety first answer shows many correct MT usages and incorrect MT usages. Looking at that answer, I see at least 3 distinct”objects” that could be shared mutable:

  1. global_instance variable —  a shared mutable object. race condition 😦
  2. control block — shared by different club members. Any one club member, like global_instance could be a shared mutable object. Concurrent access the control block is managed by the shared_ptr implementation
  3. pointee on heap — is shared  mutable. If 2 threads call mutator methods on this object, you can hit race condition.

 

 

 

%%rwlock using basic mutex

Q: implement a simple read/write lock using a basic mutex provided by a basic thread library.

I feel some minimum fairness is needed. You don’t want a writer thread to keep waiting for a long time while readers come and go, as if the writer is a second-class citizen.

Ashish pointed out a bug, so now, all 4 methods must acquire _police from onset, before reading/writing  internal shared data.

#include <cassert>
using namespace std;
class mutex{ //this class should be provided to us
public:
  void release();
  void acquire();
};

class raiiLock{//when an instance goes out of scope on the stack, dtor will release the lock
  mutex * const _lock;
public:
  raiiLock(mutex * m) : _lock(m){
    _lock->acquire();
  }
  ~raiiLock(){
    _lock->release();
  }
};

/*This implementation favors writers to readers, assuming write is more urgent.
Reader starvation is considered tolerable.

Since there are 2 mutexes involved, we will always acquire the police first. Helps prevent deadlocks

-- Target Usage Scenario --
Imagine a data table that any thread can read in the absence of concurrent updates,
but only one writer thread at a time can update.
*/
class rwlock{
  rwlock(rwlock const & other); //disabled
  rwlock & operator =(rwlock const & other); //disabled

  // data members
  mutex * const _mutex; //used by writers only, never by readers
  mutex * const _police; // controls modifications to internal data members
  size_t const _permits; // max number of concurrent readers allowed in theory
  size_t _available; // available permits, controlled by the police
  size_t _waitingWriters; // controlled by the police
public:
  // note dtor will be synthesized, which will do nothing if all data members are primitives or pointers
  // note no-arg ctor is not allowed.
  rwlock(mutex * m,
                mutex * police,
                size_t maxPermits):
    _mutex(m),
    _police(police),
    _permits  (maxPermits),
    _available(maxPermits),
    _waitingWriters(0){}

  void release1permit(){
    raiiLock autoLock(_police); //blocking briefly
    _available ++;
    if (_available == _permits && _waitingWriters){
      //wake up one of the writers, perhaps using a conditional variable tied to _police mutex
    }
  }
  void releaseWriterLock(){
    raiiLock autoLock(_police); //blocking briefly
    _available = _permits;
    _mutex->release();
    if (_waitingWriters){
      //wake up one of the writers, perhaps using a conditional variable tied to _police mutex
    }
  }
  char try_acquire1permit(){ // non-blocking
    //As Ashish pointed out, _police need to be acquired before 
    //checking _waitingWriters and _available. Any access to shared internal
    //data needs police protection.
    raiiLock autoLock(_police); //blocking briefly
    if (_waitingWriters) return 'w'; //immediately reject this
    // reader's request if there's any waiting writer.
    // Therefore, any subsequent reader requests will not affect the waiting writers.
    if (_available == 0 ) return 'a';

    assert( _available > 0 && _waitingWriters == 0);
    _available --;
    return '0'; // 0 means safe to read the protected data table
  }
  void acquireWriterLock(){ // blocking
    raiiLock autoLock(_police); //blocking briefly
    _waitingWriters ++ ; //Subsequent readers will be rejected, to avoid writer starvation
    while(_available < _permits){
      //block in some kind of waiting room perhaps using a conditional variable tied to _police mutex
    }
    assert (_available == _permits);
    _mutex->acquire(); // should never block
    _available = 0;
    _waitingWriters --;
    // now the current thread is free to update the table
  }
};

##common c++concurrency libraries

Java has language-level support for concurrency + some special concurrency classes in the JDK library.

C/C++ use a library-only approach. C libraries include pthreads and winthreads. I think these are provided by the OS, because these threads are kernel threads.

Many threaded c++ projects use nothing but these C libraries.

There are also c++ thread libraries, (probably always) based on the underlying C libraries.
* c++11 thread library
* boost
* ACE
* Microsoft Foundation Classes
* RogueWave

While the lucky java developer can afford to focus on a single concurrency tool set, the unlucky c++ developer often have to juggle 2 or more.

Therefore, it’s harder to get the “halo” in c++ concurrency interviews

std::lock_guard simple idiom]coding IV

Since this data type is designed as a stack object i.e. scoped variable, simply declare (and initialize) this object within curly brackets. When this variable goes out of scope, the object behind the variable gets destructed.

In fact, the compiler sets up this sequence of events.

{
std::lock_guard<std::mutex> aLocalLock(sharedMutex);
someMutableShared ++;
}

c++atomic^volatile

See other posts about volatile, in both c++ and java.

[[c++succinctly]] also firmly denounced volatile keyword’s usage for concurrency.

[[effectiveModernC++]] echos in Item 40: Use std::atomic for concurrency, volatile for special memory.

For a “volatile” variable, c++ (unlike java) compiler is not required to introduce memory fence i.e. updates by one thread is not guaranteed to be visible globally. See Eric Lippert on http://stackoverflow.com/questions/26307071/does-the-c-volatile-keyword-introduce-a-memory-fence

c++ threading support – historically

[[c++concurrencyInAction]] has a concise historical review…

* POSIX is a platform-specific C api, just like the Windows API. 
I feel this is an OS-level API. Every OS-level API tends to be in C (not c++), so various languages can all use it. OS-level API tend to “present” the hardware as is. The hardware has no concepts of object orientation or exceptions. The C language is the natural language for the hardware.
* Threading is a low-level, OS-level feature. (Kernel support? Not sure about the jargon). Naturally, threading is implemented in C and exposed first as C api. Many c++ threading developers simply use C api without any motivation to use c++.
* There's no surprise that the early c++ concurrency libraries are wrappers over the existing C api. Examples include MFC and Boost.
* Note low-level C api is usually platform-dependent. The c++ wrapper api has an opportunity to smooth out the platform differences.

sample code showing boost scoped_lock#not c++14

You need to view the post to see the attached source code!

<![CDATA[ #include #include #include #include #include using namespace std; boost::posix_time::milliseconds sleepTime(1); templateclass MyQueue { public: void enqueue(const T& x) { cout < enqueuing … ” << x <mutex_); cout <> just got lock … ” << x <mutex_); if (list_.empty()) { throw 0; // unlock } T tmp = list_.front(); list_.pop_front(); cout << "< dequeu " << tmp << "\n"; return (tmp); } private: std::list list_; boost::mutex mutex_; }; MyQueue queueOfStrings; int reps = 5; void sendSomething() { for (int i = 0; i < reps; ++i) { stringstream st; st << i; queueOfStrings.enqueue("item_" + st.str()); boost::this_thread::sleep(sleepTime); } } void recvSomething() { for (int i = 0; i < reps*3; ++i) { try { queueOfStrings.dequeue(); } catch (int ex) { cout << "<- – ( ) after releasing lock, catching " << ex <

"volatile" in c++ #hardware eg

(See other posts on volatile.) I now get more and more questions on this keyword.

Now i think a volatile variable can be updated by another thread, another process, or by hardware (via interrupts). P206 [[from c to c++]] has a “hardware” example.

P178 [[understanding and using c pointers]] shows how to read a hardware port, declared as a const pointer to a volatile integer object —

    unsigned int volatile * const port = ….

Unlike in java, Volatile can also be used on regular variables, methods, fields. Similar restriction as “const”.