I just want to confirm it's safe to reset to the same smart pointer with lock in multi-threads? if there is no lock_guard, it's not safe due to it is not a thread-safe method? I suppose reset is not threadsafe, however, no crash is observed if I removed the lock.
class foo {
public:
foo()
{
std::cout << "foo constructed" << std::endl;
}
~foo()
{
std::cout << "foo destructed" << std::endl;
}
};
int main(int argc, const char * argv[]) {
std::shared_ptr<foo> f = std::make_shared<foo>();
conqueue = dispatch_queue_create("MyConcurrentDiapatchQueue", DISPATCH_QUEUE_CONCURRENT);
static std::mutex io_mutex;
for (int i = 0; i < 100000; i++)
{
dispatch_async(conqueue, ^{
std::lock_guard<std::mutex> lk(io_mutex); // no crash without this line as well
f.reset(); // it's safe? No crash if I reset the same shared_ptr in multi-threads.
});
}
return 0;
}
The shared_ptr object is not thread-safe, nor is the pointed-to object. Only the reference count is thread-safe. So yes, you need to use a guard.
In C++20, there is std::atomic<std::shared_ptr<T>>.
Documentation don't guarantee safety of that component. Essentially if standard says nothing about it then your assumption is right. You have to have that lockquard or anything that provides same functionality.
Crash might be no observable because you don't have racing condition until you try read pointer in a concurrent thread because you don't actually try to use that value (all that reset does is to change pointer value).
Well and you can't rely on that you can't observe an UB, UB is a Schrodinger's cat.
Related
Let's say I start a new thread from a classmethod and pass "this" as a parameter to the lambda of the new thread. If the object is destroyed before the thread uses something from "this", then it's probably undefined behavior.
As a simple example:
#include <thread>
#include <iostream>
class Foo
{
public:
Foo() : m_bar{123} {}
void test_1()
{
std::thread thd = std::thread{[this]()
{
std::cout << m_bar << std::endl;
}};
thd.detach();
}
void test_2()
{
test_2(m_bar);
}
void test_2(int & bar)
{
std::thread thd = std::thread{[this, & bar]()
{
std::cout << bar << std::endl;
}};
thd.detach();
}
private:
int m_bar;
};
int main()
{
// 1)
std::thread thd_outer = std::thread{[]()
{
Foo foo;
foo.test_1();
}};
thd_outer.detach();
// 2)
{
Foo foo;
foo.test_1();
}
std::cin.get();
}
The outcomes
(For the original project, I have to use VS19, so the exception messages are originally coming from that IDE.)
Starting from thd_outer, test_1 and test_2 are either throwing an exception (Exception thrown: read access violation.) or printing 0 (instead of 123).
Without thd_outer they seem correct.
I've tried the same code with GCC under Linux, and they always print 123.
Which one is the correct behavior? I think it is UB, and in that case all are "correct". If it's not undefined, then why are they different?
I would expect 123 or garbage always because either the object is still valid (123) or was valid but destroyed and a) the memory is not reused yet (123) or reused (garbage). An exception is reasonable but what exactly is throwing it (VS only)?
I've came up with a possible solution to the problem:
class Foo2
{
public:
Foo2() : m_bar{123} {}
~Foo2()
{
for (std::thread & thd : threads)
{
try
{
thd.join();
}
catch (const std::system_error & e)
{
// handling
}
}
}
void test_1()
{
std::thread thd = std::thread{[this]()
{
std::cout << m_bar << std::endl;
}};
threads.push_back(std::move(thd));
}
private:
int m_bar;
std::vector<std::thread> threads;
};
Is it a safe solution, without undefined behaviors? Seems like it's working. Is there a better and/or more "standardized" way?
Forget about member variables or classes. The question then is, how do I make sure that a thread does not use a reference to an object that has been destroyed. Two approaches exist that both effectively ensure that the thread ends before the object is destroyed plus a third one that's more complicated.
Extend the object lifetime to that of the thread. The easiest way is to use dynamic allocation of the object. In addition, to avoid memory leaks, use smart pointers like std::shared_ptr.
Limit the thread runtime to that of the object. Before destroying the object, simply join the thread.
Tell the thread to let go of the object before destroying it. I'll only sketch this, because its the most complicated way, but if you somehow tell the thread that it must not use the object any more, you can then destroy the object without adverse side effects.
That said, one advise: You are sharing an object between (at least) two threads. Accessing it requires synchronization, which is a complex topic in and of itself.
According to cppref, accessing const members of shared_ptr across multiple threads is safe. But is this statement valid when we have a weak_ptr that corresponds to a shared_ptr?
As an example assume the following code:
#include <memory>
#include <iostream>
#include <thread>
std::shared_ptr<int> sp;
std::weak_ptr<int> gw;
int main()
{
sp = std::make_shared<int>(42);
gw = sp;
auto th1 = std::thread([]{
for (int i = 0; i < 200; i++) {
if (sp.use_count() > 1) {
std::cout << i << "\n";
std::this_thread::yield();
}
}
});
auto th2 = std::thread([]{
for (int i = 0; i < 20; i++) {
if (auto l = gw.lock()) {
std::cout << "locked ->" << l.use_count() << "\n";
std::this_thread::yield();
}
}
});
th1.join();
th2.join();
}
This code creates 2 threads. One checks use_count() of shared_ptr() which is a const method and the other one use lock() to lock weak_ptr() which also is a const method too. But in reality, when I call lock on weak_ptr, I practically increase reference count of shared_ptr which is not thread safe unless reference count is internally guarded. I wonder if I will have a data race in situations like this. Is this supposed to be thread-safe by standard?
Yes. The reference counter is atomic, so there are no data races in your example.
That being said, mutable operations on objects pointed by std::shared_ptr are not atomic, so they must be guarded as you would guard access via a plain pointer.
I know there are a lot of similar questions with answers around, but since I still don't understand this particular case, I decided to pose a question.
What I have is a map of shared_ptrs to a dynamically allocated array (MyVector). What I want is limited concurrent access without the need to lock. I know that the map per se is not thread safe, but I always thought what I'm doing here should be ok, which is:
I fill the map in a single threaded environment like that:
typedef shared_ptr<MyVector<float>> MyVectorPtr;
for (int i = 0; i < numElements; i++)
{
content[i] = MyVectorPtr(new MyVector<float>(numRows));
}
After the initialization, I have one thread that reads from the elements and one that replaces what the shared_ptrs point to.
Thread 1:
for(auto i=content.begin();i!=content.end();i++)
{
MyVectorPtr p(i->second);
if (p)
{
memory_use+=sizeof(int) + sizeof(float) * p->number;
}
}
Thread 2:
for (auto itr=content.begin();content.end()!=itr;++itr)
{
itr->second.reset(new MyVector<float>(numRows));
}
After a while I get either a seg fault or a double free in one of the two threads. Somehow not really surprisingly, but still I don't really get it.
The reasons why I thought this would work, are:
I don't add or remove any items of the map in the multi-threaded
environment, so the iterators should always point to something valid.
I thought concurrently changing a single element of the map is fine as long as the operation is atomic.
I thought the operations I do on the shared_ptr (increment ref count, decrement ref count in Thread 1, reset in Thread 2) are atomic. SO Question
Obviously, either one ore more of my assumptions are wrong, or I'm not doing what I think I am. I think that reset actually is not thread safe, would std::atomic_exchange help?
Can someone release me? Thanks a lot!
If someone wants to try out, here is the full code example:
#include <stdio.h>
#include <iostream>
#include <string>
#include <map>
#include <unistd.h>
#include <pthread.h>
using namespace std;
template<class T>
class MyVector
{
public:
MyVector(int length)
: number(length)
, array(new T[length])
{
}
~MyVector()
{
if (array != NULL)
{
delete[] array;
}
array = NULL;
}
int number;
private:
T* array;
};
typedef shared_ptr<MyVector<float>> MyVectorPtr;
static map<int,MyVectorPtr> content;
const int numRows = 1000;
const int numElements = 10;
//pthread_mutex_t write_lock;
double get_cache_size_in_megabyte()
{
double memory_use=0;
//BlockingLockGuard guard(write_lock);
for(auto i=content.begin();i!=content.end();i++)
{
MyVectorPtr p(i->second);
if (p)
{
memory_use+=sizeof(int) + sizeof(float) * p->number;
}
}
return memory_use/(1024.0*1024.0);
}
void* write_content(void*)
{
while(true)
{
//BlockingLockGuard guard(write_lock);
for (auto itr=content.begin();content.end()!=itr;++itr)
{
itr->second.reset(new MyVector<float>(numRows));
cout << "one new written" <<endl;
}
}
return NULL;
}
void* loop_size_checker(void*)
{
while (true)
{
cout << get_cache_size_in_megabyte() << endl;;
}
return NULL;
}
int main(int argc, const char* argv[])
{
for (int i = 0; i < numElements; i++)
{
content[i] = MyVectorPtr(new MyVector<float>(numRows));
}
pthread_attr_t attr;
pthread_attr_init(&attr) ;
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_t *grid_proc3 = new pthread_t;
pthread_create(grid_proc3, &attr, &loop_size_checker,NULL);
pthread_t *grid_proc = new pthread_t;
pthread_create(grid_proc, &attr, &write_content,(void*)NULL);
// to keep alive and avoid content being deleted
sleep(10000);
}
I thought concurrently changing a single element of the map is fine as long as the operation is atomic.
Changing the element in a map is not atomic unless you have a atomic type like std::atomic.
I thought the operations I do on the shared_ptr (increment ref count, decrement ref count in Thread 1, reset in Thread 2) are atomic.
That is correct. Unfortunately you are also changing the underlying pointer. That pointer is not atomic. Since it is not atomic you need synchronization.
One thing you can do though is use the atomic free functions that are introduced with std::shared_ptr. This will let you avoid having to use a mutex.
Lets expand MyVectorPtr p(i->second); which is running on thread-1:
The constructor called for this is:
template< class Y >
shared_ptr( const shared_ptr<Y>& r ) = default;
Which probably boils down to 2 assignments of the underlying shared pointer and the reference count.
It may very well happen that thread 2 would delete the shared pointer while in thread-1 the pointer is being assigned to p. The underlying pointer stored inside shared_ptr is not atomic.
Thus, you usage of std::shared_ptr is not thread safe. It is thread safe as long as you do not update or modify the underlying pointer.
TL;DR;
Changing std::map isn't thread safe, while using std::shared_ptr regarding additional references is.
You should protect accessing your map regarding read/write operations using an appropriate synchronization mechanism, like e.g. a std::mutex.
Also if the state of an instance referenced by the std::shared_ptr should change, it needs to be protected against data races if it's accessed from concurrent threads.
BTW, the MyVector you are showing is a way too naive implementation.
I have no idea why my code doesn't terminate.
It is probably some obvious thing I miss here, please help!
using namespace std;
int main(int argc, char* argv[])
{
MyClass *m = new MyClass();
thread t1(th,m);
delete m;
m=NULL;
t1.join();
return 0;
}
void th(MyClass *&p)
{
while(p!=NULL)
{
cout << "tick" << endl;
this_thread::sleep_for(chrono::seconds(1));
}
return;
}
The thread is being given a copy of m, not a reference to it. Use a reference wrapper to give it a reference:
thread t1(th,std::ref(m));
The program will probably end as expected then; but you still have undefined behaviour due to the data race of modifying m on one thread, and reading it on another without synchronisation. To fix that, either use std::atomic<MyClass*>, or protect both accesses with a mutex.
I'm learning concurrent programming and what I want to do is have a class where each object it responsible for running its own Boost:Thread. I'm a little over my head with this code because it uses A LOT of functionality that I'm not that comfortable with (dynamically allocated memory, function pointers, concurrency etc etc). It's like every line of code I had to check some references to get it right.
(Yes, all allocated memory is accounted for in the real code!)
I'm having trouble with the mutexes. I declare it static and it seems to get the same value for all the instances (as it should). The code is STILL not thread safe.
The mutex should stop the the threads (right?) from progressing any further in case someone else locked it. Because mutexes are scoped (kind of a neat functionality) and it's within the if statement that should look the other threads out no? Still I get console out puts that clearly suggests it is not thread safe.
Also I'm not sure I'm using the static vaiable right. I tried different ways of refering to it (Seller::ticketSaleMutex) but the only thing that worked was "this->ticketSaleMutex" which seems very shady and it seems to defeat the purpose of it being static.
Seller.h:
class Seller
{
public:
//Some vaiables
private:
//Other variables
static boost::mutex ticketSaleMutex; //Mutex definition
};
Seller.cpp:
boost::mutex Seller::ticketSaleMutex; //Mutex declaration
void Seller::StartTicketSale()
{
ticketSale = new boost::thread(boost::bind(&Seller::SellTickets, this));
}
void Seller::SellTickets()
{
while (*totalSoldTickets < totalNumTickets)
{
if ([Some time tick])
{
boost::mutex::scoped_lock(this->ticketSaleMutex);
(*totalSoldTickets)++;
std::cout << "Seller " << ID << " sold ticket " << *totalSoldTickets << std::endl;
}
}
}
main.cpp:
int main(int argc, char**argv)
{
std::vector<Seller*> seller;
const int numSellers = 10;
int numTickets = 40;
int *soldTickets = new int;
*soldTickets = 0;
for (int i = 0; i < numSellers; i++)
{
seller.push_back(new Seller(i, numTickets, soldTickets));
seller[i]->StartTicketSale();
}
}
This will create a temporary that is immediately destroyed:
boost::mutex::scoped_lock(this->ticketSaleMutex);
resulting in no synchronization. You need to declare a variable:
boost::mutex::scoped_lock local_lock(this->ticketSaleMutex);