6

I need some help for understanding general basics of shared resource access in C++ multithreading.

When I need some variable that must be accessible for several threads, I declare it as atomic, or synchronize operations with it by mutex.

But what if some function/method required pointer to type, and I must protect my variable(shared resource) for read/write in multiply threads. I know that in C and C++ pointers is passed by value, but anyway that confused me.

Question. Can I need to lock pointer to shared resource too:

void foo (int * pi = nullptr)
{
    //{..} some ops

    static std::mutex mtx;

    std::unique_lock<std::mutex> lock(mtx); // lock mutex BEFORE ptr check

    if(pi){

        ++(*pi); // dereference ptr's shared resource

    }

    lock.unlock();

    //{...} some ops
}

OR lock only for resource itself:

void foo (int * pi = nullptr)
{
    //{..} some ops
    
    static std::mutex mtx;
    
    if(pi){ // check if ptr is not null, is this thread-safe ?
       
        std::unique_lock<std::mutex> lock(mtx); // lock mutex ONLY for access shared resource itself, 
                                                // not for pointer that refers to it
        
        ++(*pi); // dereference ptr's shared resource
        
        lock.unlock();
    }


    //{...} some ops
}

Here some dumb "synthetic" example call with shared resource:

int main(){

    int __i = 0;

    std::thread t([&__i](){

        for(;;){

            foo(&__i);
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }
    });

    for(;;){

        foo(&__i);
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        
        //std::cout << __i << std::endl;
    }

    t.join();
}

P.S. 'int' type in this example only for clarity.

5
  • 1
    A side note: I wouldn't create an identifier that start with __ (although I think that only those starting with _ and a capital letter are strictly reserved). Commented Sep 19 at 14:05
  • 3
    "When I need some variable that must be accessible for several threads, I declare it as atomic, or..." That is a naive way of thinking about synchronization. At some point, you want to start thinking about invariants. An invariant is a relationship between two or more variables that must always be true. Trivial example: You might have three ints whose sum must always be zero. It is impossible for any thread to change any of those variables without temporarily "breaking" the invariant. Synchronization is how we prevent other threads from seeing the temporary, broken state. Commented Sep 19 at 16:02
  • 1
    @wohlstad • An identifier with double-underscore __ anywhere in it is reserved, and declaring/defining such an identifier in one's code is undefined behavior. Your first inclination is correct. Commented Sep 19 at 17:13
  • @Eljay good to know (I wasn't sure). Commented Sep 19 at 17:20
  • This question is unclear because of the poor grammar. Can you please edit it to clarify what you mean using valid English grammar/syntax? Commented Oct 8 at 19:51

2 Answers 2

13

In your example, you don't need to lock before checking if pi is a nullptr since nothing from the outside can change what pi is pointing at. Your second example is therefore sufficient.

Sign up to request clarification or add additional context in comments.

Comments

6

Pointers aren't really any different from other types in this respect.

Regardless of type, multiple threads can read from the same item without synchronization, as long as none of them does any writing.

If any thread writes to shared data (again, regardless of type) you need to use a mutex (or atomic variable, etc.) to avoid race conditions.

In your case, all the threads are reading from the pointer (but never writing to it) so no mutex is needed for it.

They are modifying what the pointer refers to, so you do need a mutex for that.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.