[thread] Use of synchronized_value<> member in a constructor
I am using now Boost 1.54.0 Beta (r84749). I am writting code similar to the following (but a bit more complex) : struct SequenceInfo { SequenceId id; ProjectId project_id; std::string name; //.... }; class Sequence { boost::synchronized_value<SequenceInfo> m_info; public: explicit Sequence( SequenceId new_id, SequenceInfo info ) : m_info( std::move(info) ) { m_info->id( new_id ); // mutex lock } // ... }; My current understanding of concurrent access practice is that it is not necessary to use synchronization mechanisms associated to member objects manipulated into a constructor, because it can happen on only one thread. If I am correct, then the lock of the synchronized_value is unnecessary. I suspect that there might be other cases like this one where the object responsable for the synchronized object should be able, rarely, to access the object unsafely. Suggestion: add an unsafe access to the object, in a very visible way. m_info.unsafe_access().id( new_id ); OR m_info.unsafe_access()->id( new_id ); // using a smart pointer which checks if safe accesses have been done inbetween and throw if it is the case? or any other checks detecting potential error It looks like it would allow avoiding cost where it could be avoided using a mutex manipulated manually. Any thoughts on this? Joel Lamotte
Le 12/06/13 21:29, Klaim - Joël Lamotte a écrit :
I am using now Boost 1.54.0 Beta (r84749).
I am writting code similar to the following (but a bit more complex) :
struct SequenceInfo { SequenceId id; ProjectId project_id; std::string name; //.... };
class Sequence { boost::synchronized_value<SequenceInfo> m_info;
public:
explicit Sequence( SequenceId new_id, SequenceInfo info ) explicit is not needed here. : m_info( std::move(info) ) { m_info->id( new_id ); // mutex lock You mean
m_info->id = new_id; ? I would define thesame kind of constructor for the class SequenceInfo . Sequence( SequenceId new_id, SequenceInfo info ) : m_info( new_id, std::move(info) ) {}
} // ... };
My current understanding of concurrent access practice is that it is not necessary to use synchronization mechanisms associated to member objects manipulated into a constructor, because it can happen on only one thread.
Right.
If I am correct, then the lock of the synchronized_value is unnecessary.
You don't need to lock at construction. but once constructed the lock is imperative.
I suspect that there might be other cases like this one where the object responsable for the synchronized object should be able, rarely, to access the object unsafely. For example?
Suggestion: add an unsafe access to the object, in a very visible way.
m_info.unsafe_access().id( new_id );
OR
m_info.unsafe_access()->id( new_id ); // using a smart pointer which checks if safe accesses have been done inbetween and throw if it is the case? or any other checks detecting potential error I don't think this is needed for the use case presented. Just create the appropiated constructors. It looks like it would allow avoiding cost where it could be avoided using a mutex manipulated manually.
Any thoughts on this? I'm open to your suggestion if you have other use cases that can not be solved otherwise.
Best, Vicente
Thanks for your patience. On Wed, Jun 12, 2013 at 10:47 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
explicit Sequence( SequenceId new_id, SequenceInfo info )
explicit is not needed here.
In C++11 it prevent returning a temporary with more than one attributes in the constructor without using the type name explicitly: // returning a Sequence return { sequence_id, some_info }; // error (in C++11): the constructor is explicit return Sequence{ sequence_id, some_info }; // OK (C++11) I don't have the standard around to check but here is an explanation: http://stackoverflow.com/questions/12437241/c-always-use-explicit-constructo... (note that it's just an example, in my real code Sequence is not a value semantic type)
: m_info( std::move(info) )
{ m_info->id( new_id ); // mutex lock
You mean
m_info->id = new_id;
?
Yes, sorry, in my current code id is a (setter) function so I was a bit confused but you got the idea correctly.
I would define thesame kind of constructor for the class SequenceInfo .
Sequence( SequenceId new_id, SequenceInfo info ) : m_info( new_id, std::move(info) ) {}
}
// ... };
This example is a contrived/simplistic one. In my real code the member I'm setting is from generated code which I can't modify without important efforts. Assume that I'm not in the control of that member's type definition. The best I could do in this case, I suppose, would be a function wrapper:
Sequence( SequenceId new_id, SequenceInfo info ) : m_info( merge_info( new_id, info ) ) // no locking {} Which should be equivalent to your suggestion I guess, but it is not enough in all cases. See below.
If I am correct, then the lock of the synchronized_value is unnecessary.
You don't need to lock at construction. but once constructed the lock is imperative.
Just to clarify, and make sure I understand 100% correctly, are you talking about the member's construction (m_info) or the owning object construction (Sequence)? I assume the later, so the member don't need locking until the end of the constructor of the owning object.
I suspect that there might be other cases like this one where
the object responsable for the synchronized object should be able, rarely, to access the object unsafely.
For example?
This is just speculation. Maybe I'm just pessimistic. Currently I only have the case of the constructor and I don't think I will have others. I have another bigger code base which have more complex concurrent cases where I might find similar usage (access to a usually locked object in a case where I am 100% sure there can't be concurrent accesses). I will search and report if I find something, I'm not sure I will.
Suggestion: add an unsafe access to the object, in a very visible way.
m_info.unsafe_access().id( new_id );
OR
m_info.unsafe_access()->id( new_id ); // using a smart pointer which checks if safe accesses have been done inbetween and throw if it is the case? or any other checks detecting potential error
I don't think this is needed for the use case presented. Just create the appropiated constructors.
As I was saying, in the real code I will have to use a builder function instead, I can't modify that type. However it is still not a perfect solution. In the real code I have some logic I need to do before manipulating the synchronized member. It should be done in this order, first some logic, then use thesynched object. But even if I use a function to create the member but still have to access it later because of that logic, there is no point in doing that. To clarify what I'm trying to explain (this is fictive code close to mine, which is more complex): explicit Sequence( SequenceId new_id, SequenceInfo info ) : m_info( merge_info( new_id, info ) ) // assuming I follow your suggestion { if( first_thing_to_do() ) { second_thing_to_do( m_info->name ); // still need to lock } } I'm using function name instead of real code just so you get the idea, sorry if it's not real code. I try to do minimum work in the constructor but I noticed the locking access when I couldn't avoid it. I will try to be clearer with some code, first this: class InfoProcessor; // generate analytic data from info - non-copyable! class Sequence { boost::synchronized_value<InfoProcessor> m_infoproc; public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // process info into the constructor, ready to be used { m_infoproc->do_something(); // can't avoid a lock? } Sequence( const Sequence& ) = delete; Sequence& operator=( const Sequence& ) = delete; Assuming I can't modify InfoProcessor, the only way I see how to do the call in the constructor without locking would be to encapsulate the member into a wrapper: class Sequence { struct InfoProcWrapper { InfoProcWrapper( SequenceInfo info ) : infoproc( info ) { infoproc.do_something(); } // no locking InfoProcessor infoproc; }; boost::synchronized_value<InfoProcWrapper > m_infoproc; public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // now any access will need locking { // OK: m_infoproc->infoproc.do_something() was already called without locking. } Which would be fine if it wasn't so noisy and that's only for 1 synchronized member... class Sequence { struct InfoProcWrapper { InfoProcWrapper( SequenceInfo info ) : infoproc( info ) { infoproc.do_something(); } // no locking InfoProcessor infoproc; }; struct DataCruncherWrapper { DataCruncherWrapper( SequenceInfo info ) : cruncher( info ) { cruncher.prepare( 42 ); } // no locking DataCruncher cruncher; }; boost::synchronized_value<InfoProcWrapper> m_infoproc; boost::synchronized_value<DataCruncher> m_cruncher; // should NOT be synched with the same mutex public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // OK: setup with no locking : m_cruncher( info ) // OK: setup with no locking { m_cruncher->cruncher.configure( m_infoproc->infoproc ); // how to do that without locking? } void crunch( Data data ) { m_cruncher->cruncher.crunch( data ); } // OK: locks only one mutex void add( Element element ) { m_infoproc->infoproc.add( element ); } // OK: locks only one mutex Anyway, I might be wrong but it seems to me that any attempt to combine use of several synched members in the constructor will imply locking OR adding several layers of abstractions, which if the members' types have value-semantic, can be easy, but if it's not the case, it becomes quickly very noisy. Obviously it is still just speculation but it don't seem unfamiliar to have this kind of work in a constructor. It looks like it would allow avoiding cost where it could be avoided using
a mutex manipulated manually.
Any thoughts on this?
I'm open to your suggestion if you have other use cases that can not be solved otherwise.
I will have to find a concrete and more convincing use case and report here then. Note that hopefully the cost of the lock isn't important so far in my specific use case of today, I was just pointing the constructor cost because in the other code bases I have I might have more performance-related concerns and I might not be the only one. Joel Lamotte
Le 13/06/13 01:07, Klaim - Joël Lamotte a écrit :
Thanks for your patience.
On Wed, Jun 12, 2013 at 10:47 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
explicit Sequence( SequenceId new_id, SequenceInfo info )
explicit is not needed here.
In C++11 it prevent returning a temporary with more than one attributes in the constructor without using the type name explicitly:
// returning a Sequence return { sequence_id, some_info }; // error (in C++11): the constructor is explicit return Sequence{ sequence_id, some_info }; // OK (C++11)
I don't have the standard around to check but here is an explanation: http://stackoverflow.com/questions/12437241/c-always-use-explicit-constructo... I was not aware of this C++11 feature. Thanks.
(note that it's just an example, in my real code Sequence is not a value semantic type)
I would define thesame kind of constructor for the class SequenceInfo .
Sequence( SequenceId new_id, SequenceInfo info ) : m_info( new_id, std::move(info) ) {}
This example is a contrived/simplistic one. In my real code the member I'm setting is from generated code which I can't modify without important efforts. Assume that I'm not in the control of that member's type definition. The best I could do in this case, I suppose, would be a function wrapper:
Sequence( SequenceId new_id, SequenceInfo info ) : m_info( merge_info( new_id, info ) ) // no locking {}
Which should be equivalent to your suggestion I guess, This seems a good alternative. but it is not enough in all cases. See below.
If I am correct, then the lock of the synchronized_value is unnecessary.
You don't need to lock at construction. but once constructed the lock is imperative.
Just to clarify, and make sure I understand 100% correctly, are you talking about the member's construction (m_info) or the owning object construction (Sequence)? I meant the member. I assume the later, so the member don't need locking until the end of the constructor of the owning object. No. Any access to the member would imply a lock after construction.
m_info-> returns a lock.
I suspect that there might be other cases like this one where
the object responsable for the synchronized object should be able, rarely, to access the object unsafely.
You are supposing that there is an object responsible for synchronized object, but I don't know what this means and how to ensure it. If you want this pattern , you maybe need external locking. See the class externally_locked.
For example?
This is just speculation. Maybe I'm just pessimistic. Currently I only have the case of the constructor and I don't think I will have others. I have another bigger code base which have more complex concurrent cases where I might find similar usage (access to a usually locked object in a case where I am 100% sure there can't be concurrent accesses). Could you develop the case and explain how are you 100% sure that there is no concurrent access? I will search and report if I find something, I'm not sure I will.
Suggestion: add an unsafe access to the object, in a very visible way.
m_info.unsafe_access().id( new_id );
OR
m_info.unsafe_access()->id( new_id ); // using a smart pointer which checks if safe accesses have been done inbetween and throw if it is the case? or any other checks detecting potential error BTW, I don't know what "checks if safe accesses have been done inbetween" could mean? Could you clarify? I don't think this is needed for the use case presented. Just create the appropiated constructors.
As I was saying, in the real code I will have to use a builder function instead, I can't modify that type. This seems a good alternative.
However it is still not a perfect solution. In the real code I have some logic I need to do before manipulating the synchronized member. It should be done in this order, first some logic, then use thesynched object. But even if I use a function to create the member but still have to access it later because of that logic, there is no point in doing that. You can create a unsynchronized SequenceInfo object, do whatever you want with and last create the synchronized SequenceInfo just by copying. To clarify what I'm trying to explain (this is fictive code close to mine, which is more complex):
explicit Sequence( SequenceId new_id, SequenceInfo info ) : m_info( merge_info( new_id, info ) ) // assuming I follow your suggestion { if( first_thing_to_do() ) { second_thing_to_do( m_info->name ); // still need to lock } } I recognize that the factory function could be less natural, but it works
I'm using function name instead of real code just so you get the idea, sorry if it's not real code. I try to do minimum work in the constructor but I noticed the locking access when I couldn't avoid it.
I will try to be clearer with some code, first this:
class InfoProcessor; // generate analytic data from info - non-copyable! Is it movable?
class Sequence { boost::synchronized_value<InfoProcessor> m_infoproc;
public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // process info into the constructor, ready to be used Isn't the parameter InfoProcessor info? { m_infoproc->do_something(); // can't avoid a lock? }
Sequence( const Sequence& ) = delete; Sequence& operator=( const Sequence& ) = delete;
Assuming I can't modify InfoProcessor, the only way I see how to do the call in the constructor without locking would be to encapsulate the member into a wrapper:
class Sequence { struct InfoProcWrapper { InfoProcWrapper( SequenceInfo info ) : infoproc( info ) { infoproc.do_something(); } // no locking
InfoProcessor infoproc; }; boost::synchronized_value<InfoProcWrapper > m_infoproc;
public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // now any access will need locking { // OK: m_infoproc->infoproc.do_something() was already called without locking. }
Which would be fine if it wasn't so noisy and that's only for 1 synchronized member... I agree, creating these wrappers is too noisy but an alternative. I
SequenceInfo makeIt( SequenceId new_id, SequenceInfo info ) { info = merge_info( new_id, info ); if( first_thing_to_do() ) { second_thing_to_do( info->name ); // still need to lock } } return info } explicit Sequence( SequenceId new_id, SequenceInfo info ) : m_info( makeIt( new_id, info ) ) {} think the factory is a better choice. Note that you have lambdas in C++11.
class Sequence { struct InfoProcWrapper { InfoProcWrapper( SequenceInfo info ) : infoproc( info ) { infoproc.do_something(); } // no locking
InfoProcessor infoproc; }; struct DataCruncherWrapper { DataCruncherWrapper( SequenceInfo info ) : cruncher( info ) { cruncher.prepare( 42 ); } // no locking
DataCruncher cruncher; }; boost::synchronized_value<InfoProcWrapper> m_infoproc; boost::synchronized_value<DataCruncher> m_cruncher; // should NOT be synched with the same mutex
public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // OK: setup with no locking : m_cruncher( info ) // OK: setup with no locking { m_cruncher->cruncher.configure( m_infoproc->infoproc ); // how to do that without locking? }
Delaying the construction. This is similar to the problems raised for writing constexpr fuctions. class Sequence { struct X { InfoProcWrapper m_infoproc; DataCruncher m_cruncher; X(SequenceInfo info) : sx_( info ) : m_cruncher( info ) { m_cruncher->cruncher.configure( m_infoproc->infoproc ); } }; struct SX { boost::synchronized_value<InfoProcWrapper> m_infoproc; boost::synchronized_value<DataCruncher> m_cruncher; // should NOT be synched with the same mutex SX(X) {...}; }; SX sx_; public: explicit Sequence( SequenceInfo info ) : sx_( X(info) ) { }
void crunch( Data data ) { m_cruncher->cruncher.crunch( data ); } // OK: locks only one mutex void add( Element element ) { m_infoproc->infoproc.add( element ); } // OK: locks only one mutex
Anyway, I might be wrong but it seems to me that any attempt to combine use of several synched members in the constructor will imply locking OR adding several layers of abstractions, which if the members' types have value-semantic, can be easy, but if it's not the case, it becomes quickly very noisy.
I agree. We surely need something that make this easier and less noisy. I would prefer if we continue to see other alternatives to go directly to providing a bypass function.
Obviously it is still just speculation but it don't seem unfamiliar to have this kind of work in a constructor.
It looks like it would allow avoiding cost where it could be avoided using
a mutex manipulated manually.
Any thoughts on this?
I'm open to your suggestion if you have other use cases that can not be solved otherwise.
I will have to find a concrete and more convincing use case and report here then.
Note that hopefully the cost of the lock isn't important so far in my specific use case of today, I was just pointing the constructor cost because in the other code bases I have I might have more performance-related concerns and I might not be the only one.
I would help you if I can. HTH, Vicente
On Thu, Jun 13, 2013 at 8:13 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Just to clarify, and make sure I understand 100% correctly,
are you talking about the member's construction (m_info) or the owning object construction (Sequence)?
I meant the member.
I assume the later, so the member don't need locking until the end of the
constructor of the owning object.
No. Any access to the member would imply a lock after construction.
m_info-> returns a lock.
Ok then.
I suspect that there might be other cases like this one where
the object responsable for the synchronized object should be able, rarely, to access the object unsafely.
You are supposing that there is an object responsible for synchronized
object, but I don't know what this means and how to ensure it. If you want this pattern , you maybe need external locking. See the class externally_locked.
Ok I'll take a look.
For example?
This is just speculation. Maybe I'm just pessimistic. Currently I only have the case of the constructor and I don't think I will have others. I have another bigger code base which have more complex concurrent cases where I might find similar usage (access to a usually locked object in a case where I am 100% sure there can't be concurrent accesses).
Could you develop the case and explain how are you 100% sure that there is no concurrent access?
I'll get back to this after looking at the codebase I was talking about.
I will search and report if I find something, I'm not sure I will.
Suggestion: add an unsafe access to the object, in a very visible way.
m_info.unsafe_access().id( new_id );
OR
m_info.unsafe_access()->id( new_id ); // using a smart pointer which checks if safe accesses have been done inbetween and throw if it is the case? or any other checks detecting potential error
BTW, I don't know what "checks if safe accesses have been done
inbetween" could mean? Could you clarify?
I am not sure really, I don't think I have enough experience in the domain. I was thinking maybe there are ways to note if at least one lock have been taken while while accessing the object unsafely. Maybe just throw an exception on locking if an unsafe access is going on?
However it is still not a perfect solution. In the real code I have some logic I need to do before manipulating the synchronized member. It should be done in this order, first some logic, then use thesynched object. But even if I use a function to create the member but still have to access it later because of that logic, there is no point in doing that.
You can create a unsynchronized SequenceInfo object, do whatever you want with and last create the synchronized SequenceInfo just by copying.
Yes, when the object is copyable is less of a problem.
To clarify what I'm trying to explain (this is fictive code close to mine,
which is more complex):
explicit Sequence( SequenceId new_id, SequenceInfo info ) : m_info( merge_info( new_id, info ) ) // assuming I follow your suggestion { if( first_thing_to_do() ) { second_thing_to_do( m_info->name ); // still need to lock } }
I recognize that the factory function could be less natural, but it works
For this case I agree.
I'm using function name instead of real code just so you get the idea,
sorry if it's not real code. I try to do minimum work in the constructor but I noticed the locking access when I couldn't avoid it.
I will try to be clearer with some code, first this:
class InfoProcessor; // generate analytic data from info - non-copyable!
Is it movable?
In my case no. If it was it would be ok.
class Sequence { boost::synchronized_value<**InfoProcessor> m_infoproc;
public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // process info into the constructor, ready to be used
Isn't the parameter InfoProcessor info?
No, it's SequenceInfo. m_infoproc "unflat" data from the basic info provided.
{
m_infoproc->do_something(); // can't avoid a lock? }
Sequence( const Sequence& ) = delete; Sequence& operator=( const Sequence& ) = delete;
Assuming I can't modify InfoProcessor, the only way I see how to do the call in the constructor without locking would be to encapsulate the member into a wrapper:
class Sequence { struct InfoProcWrapper { InfoProcWrapper( SequenceInfo info ) : infoproc( info ) { infoproc.do_something(); } // no locking
InfoProcessor infoproc; }; boost::synchronized_value<**InfoProcWrapper > m_infoproc;
public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // now any access will need locking { // OK: m_infoproc->infoproc.do_**something() was already called without locking. }
Which would be fine if it wasn't so noisy and that's only for 1 synchronized member...
I agree, creating these wrappers is too noisy but an alternative. I think the factory is a better choice. Note that you have lambdas in C++11.
Yes I thought about lambdas afterward, but assuming InforProc is not movable nor copyable, the factory you are suggesting would have to create it dynamically to be able to pass it around. It's again a solution, not perfect for all cases but I guess that's ok in most cases.
class Sequence { struct InfoProcWrapper { InfoProcWrapper( SequenceInfo info ) : infoproc( info ) { infoproc.do_something(); } // no locking
InfoProcessor infoproc; }; struct DataCruncherWrapper { DataCruncherWrapper( SequenceInfo info ) : cruncher( info ) { cruncher.prepare( 42 ); } // no locking
DataCruncher cruncher; }; boost::synchronized_value<**InfoProcWrapper> m_infoproc; boost::synchronized_value<**DataCruncher> m_cruncher; // should NOT be synched with the same mutex
public: explicit Sequence( SequenceInfo info ) : m_infoproc( info ) // OK: setup with no locking : m_cruncher( info ) // OK: setup with no locking { m_cruncher->cruncher.**configure( m_infoproc->infoproc ); // how to do that without locking? }
Delaying the construction. This is similar to the problems raised for writing constexpr fuctions.
class Sequence { struct X { InfoProcWrapper m_infoproc; DataCruncher m_cruncher; X(SequenceInfo info) : sx_( info ) : m_cruncher( info )
{ m_cruncher->cruncher.**configure( m_infoproc->infoproc ); } };
struct SX {
boost::synchronized_value<**InfoProcWrapper> m_infoproc; boost::synchronized_value<**DataCruncher> m_cruncher; // should NOT be synched with the same mutex SX(X) {...}; }; SX sx_;
public: explicit Sequence( SequenceInfo info ) : sx_( X(info) )
{ }
Ok but that works only if InforProc and DataCruncher are at least movable. So, unperfect but works in a lot of cases.
Anyway, I might be wrong but it seems to me that any attempt to combine use of several synched members in the constructor will imply locking OR adding several layers of abstractions, which if the members' types have value-semantic, can be easy, but if it's not the case, it becomes quickly very noisy.
I agree. We surely need something that make this easier and less noisy. I would prefer if we continue to see other alternatives to go directly to providing a bypass function.
Yes. I am thinking maybe part of the solution would be to have a second optional argument in the synchronized_value<> constructor which would be lambda which would be called in the constructor body of the synchronized_value<>: explicit Sequence( SequenceInfo info ) : m_infoproc( info , []( InfoProcessor& infoproc ){ infoproc.do_something(); } ) // no locking - lamda called in synchronized_value<> constructor It don't solve the case where you want to use several members in the Sequence construuctor though. Is there any way in C++ to detect if we are in an object's construction? I think not. It would the opposite of the proposal for detecting when an exception have been thrown and stack-unwinding is occuring. I can't think of a better solution right now.
Note that hopefully the cost of the lock isn't important so far in my specific use case of today, I was just pointing the constructor cost because in the other code bases I have I might have more performance-related concerns and I might not be the only one.
I would help you if I can.
Thanks! Joel Lamotte
Le 13/06/13 12:43, Klaim - Joël Lamotte a écrit :
On Thu, Jun 13, 2013 at 8:13 AM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Anyway, I might be wrong but it seems to me that any attempt to combine use of several synched members in the constructor will imply locking OR adding several layers of abstractions, which if the members' types have value-semantic, can be easy, but if it's not the case, it becomes quickly very noisy.
I agree. We surely need something that make this easier and less noisy. I would prefer if we continue to see other alternatives to go directly to providing a bypass function.
Yes. I am thinking maybe part of the solution would be to have a second optional argument in the synchronized_value<> constructor which would be lambda which would be called in the constructor body of the synchronized_value<>:
explicit Sequence( SequenceInfo info ) : m_infoproc( info , []( InfoProcessor& infoproc ){ infoproc.do_something(); } ) // no locking - lamda called in synchronized_value<> constructor This could be added, but I don't think it is elegant.
It don't solve the case where you want to use several members in the Sequence construuctor though. Is there any way in C++ to detect if we are in an object's construction? I think not. It would the opposite of the proposal for detecting when an exception have been thrown and stack-unwinding is occuring.
I can't think of a better solution right now.
I was wondering if a not yet existing synchronized_value
On Sun, Jun 16, 2013 at 11:54 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Yes. I am thinking maybe part of the solution would be to have a second
optional argument in the synchronized_value<> constructor which would be lambda which would be called in the constructor body of the synchronized_value<>:
This could be added, but I don't think it is elegant.
I agree.
I was wondering if a not yet existing synchronized_value
could help in your context. You would need to declare a value and a reference. explicit Sequence( SequenceInfo info ) : m_value( info ), m_ref(M_value) { m_value.do_something(); // no need to lock.
}
It's closer to the ideal but I think it defeats the point of having only one way to access the member after the constructor is called. But the mix of this suggestion and my previous one reminded me unique_lock: you can not lock on construction with it. Maybe something similar could be done? So maybe something like that would work: explicit Sequence( SequenceInfo info ) : m_value( info , synchronized_value::dont_lock_yet ) // optional { m_value->do_something(); // don't lock! ... m_value.begin_sync(); // (random name) from here the usual locking is mandatory to access the value. m_value->do_something(); // locking } void do_something() { m_value->do_something(); // locking m_value.begin_sync(); // throw exception or UB or nothing? } I'm not sure if it would require changing the mutex concept though, but if it can be done with the current default one it would allow beginning the protection explicitly when needed but not removing it once it's active. Maybe just an atomic bool saying if the sync should happen or not, but then it's an additional test on access and with reading an atomic, which I have not a clear idea if it's expensive or not. Or maybe it would be a specialization. What do you think? Joel Lamotte
Le 17/06/13 01:36, Klaim - Joël Lamotte a écrit :
On Sun, Jun 16, 2013 at 11:54 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
I was wondering if a not yet existing synchronized_value
could help in your context. You would need to declare a value and a reference. explicit Sequence( SequenceInfo info ) : m_value( info ), m_ref(M_value) { m_value.do_something(); // no need to lock.
}
It's closer to the ideal but I think it defeats the point of having only one way to access the member after the constructor is called. Right. The constructor of the instance could master the access to the unsynchronized value. It is similar to the wrapper solution.
class SequenceImpl { X m_value1; Y m_value2; public: synchronized_value<X> m_ref1; // only the synchronized parts are public. synchronized_value<X> m_ref2; SequenceImpl(SequenceInfo info) : m_value1(info), m_value2(info), m_ref1(m_value1), m_ref2(m_value2) { // Do whatever you want with m_value1, m_value2 without locking. } }; class Sequence : public SequenceImpl { public: Sequence(SequenceInfo info) : SequenceImpl(info) { } }; Some measures would be needed to ensure that there is no lost of efficiency while using a reference instead of a value in synchronized_value.
But the mix of this suggestion and my previous one reminded me unique_lock: you can not lock on construction with it. Maybe something similar could be done? So maybe something like that would work:
explicit Sequence( SequenceInfo info ) : m_value( info , synchronized_value::dont_lock_yet ) // optional { m_value->do_something(); // don't lock! ... m_value.begin_sync(); // (random name) from here the usual locking is mandatory to access the value.
m_value->do_something(); // locking }
void do_something() { m_value->do_something(); // locking m_value.begin_sync(); // throw exception or UB or nothing? }
I'm not sure if it would require changing the mutex concept though, but if it can be done with the current default one it would allow beginning the protection explicitly when needed but not removing it once it's active. Maybe just an atomic bool saying if the sync should happen or not, but then it's an additional test on access and with reading an atomic, which I have not a clear idea if it's expensive or not. Or maybe it would be a specialization.
What do you think?
This will make the whole lifetime of the instance less efficient, so globally you will lost efficiency. I don't think it is worth the effort to change anything. * The lost of efficiency should be minor and only on the constructor when the user can not use a factory (the type T is not copyable nor movable). * The solutions make the design must more complex. Best, Vicente
participants (2)
-
Klaim - Joël Lamotte
-
Vicente J. Botet Escriba