1

I have an std::function object that many threads access. Threads can can change the function it points to (through its copy constructor) and can call the function it points to.

So, my question is, that in what ways is the std::function object itself thread safe? I'm not talking about the function it points to/calls, I'm talking about the std::function object itself.

There are surprisingly only a few sites on the internet that talk about this, and all of them (what I encountered) talk about the function it points to, not the object itself.

These are the main sites I've read:

Does std::function lock a mutex when calling an internal mutable lambda?

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4348.html

If you read them, you can see that none of them talk about the safety of the std::function object itself.

According to the second answer on this page, the STL library is thread safe in these cases:

  • simultaneous reads of the same object are OK

  • simultaneous read/writes of different objects are OK

Based on this, when I'm setting the function the std::function object points to, for this use case the std::function object is not thread safe. But I'm asking just in case it is, because according to this question's answer, it has some kind of internal mutex.

So, what way can I make it thread safe? One option would be using mutexes, but that won't fully work due to the example demonstrated below:

Suppose I have an std::function object func and std::mutex m to protect func. I lock m and call the function (to protect other threads from setting func while the function it points to is being called) and will unlock m when the function exits. Lets say this function call will take very long. This way, when one thread wants to set func, it will attempt to lock the mutex, but will be blocking until the latter function call exits. What can I do in this case?

I thought of using a pointer that will point to an std::function object. This way, I will use the mutex to protect this pointer and when I will make a function call, I lock the mutex just for getting retrieving the function object from the pointer.

Gabor Szita
  • 319
  • 4
  • 13
  • *"Threads can can change the function it points to"* - then it needs to be protected, just like everything else that isn't instinctively atomic. *Calling* the function it points to is as thread-safe as the code being called; no more, and no less. And even if you make a copy in the first case, you *still* need protection, so long as there is a potential *writer* somewhere in the mix. – WhozCraig Apr 21 '21 at 10:58
  • unless something is explicitly documented as thread safe you have to assume no thread safety – 463035818_is_not_an_ai Apr 21 '21 at 11:19

2 Answers2

3

A function object is like all other objects. Just data, and there is no built in mutexes in std::function.

If you want to protect the function with a mutex you yourself would need to create a mutex and use when interacting with function (pointing it to something else or calling it).

#include <functional>
#include <mutex>
#include <iostream>

int main() {
    std::mutex functionMutex;


    std::function<void()> f = [&functionMutex] {
        std::cout << "hello\n" << std::endl;
    };

    { // Imagine a new thread
        // Procect when calling
        auto s = std::scoped_lock{functionMutex};
        f();
    }

    { // You could wrap it in another function
       auto call = [&f, &functionMutex] {
         auto s = std::scoped_lock{functionMutex};
         f();
       };

       // Using this pointer would be safe
       call();
    }
    
    { // Imagine another thread
        auto s = std::scoped_lock{functionMutex};
        f = [] {}; // Changing the function "pointer"
    }
}
Lasersköld
  • 2,028
  • 14
  • 20
1

You can change std::function by assignment or swap. It is quite obvious that you need to protect accesses to it, like to any other non-atomic object you assign to. You usually do not change an object by a copy constructor, because a copy constructor creates a new object.

In order to protect accesses to a std::function object, you don't need a pointer.

std::function<void()> fun, funCopy, otherFun;
std::mutex mutex;

// thread 1
{ 
   auto s = std::scoped_lock{mutex};
   funCopy = fun;
}
funCopy();

// thread 2
{ 
   auto s = std::scoped_lock{mutex};
   fun = otherFun;
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243