0% found this document useful (0 votes)
98 views37 pages

Effective Modern C++ Live! Scott Meyers On Void Futures

The document discusses using void futures for one-shot event communication between concurrent tasks. Void futures allow tasks to block until an event occurs, without needing additional synchronization mechanisms like condition variables and flags. This avoids issues like spurious wakeups and tasks polling unnecessarily. An example is given of how void futures could be used to start a thread in a suspended state until another task resumes it.

Uploaded by

Lalit Joshi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
98 views37 pages

Effective Modern C++ Live! Scott Meyers On Void Futures

The document discusses using void futures for one-shot event communication between concurrent tasks. Void futures allow tasks to block until an event occurs, without needing additional synchronization mechanisms like condition variables and flags. This avoids issues like spurious wakeups and tasks polling unnecessarily. An example is given of how void futures could be used to start a thread in a suspended state until another task resumes it.

Uploaded by

Lalit Joshi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 37

Last  

Revised:  3/30/15  11:03  PM


Consider  void  Futures  for  
One-­‐‑Shot  Event  Communication
Scenario:
§  Concurrent  tasks  T1  and  T2  begin.
§  Among  T1’s  work  is  some  action  A.
§  At  some  point,  T2  may  continue  only  if  A  has  occurred.
Task  T1
Task  T2

   
…   …  
Do  A   Block  until  A’s  done  
…   …  


How  have  T1  tell  T2  that  A  has  taken  place?

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  2
Poll  (Multi-­‐‑Choice  Single  Answer)
How  would  you  approach  this  problem?
§  Use  a  condition  variable:  T1  notifies  T2  when  A  has  occurred.
§  Use  a  flag:  T2  polls  a  flag  that  T1  sets  when  A  has  occurred.
§  Use  a  condition  variable  and  a  flag:  Avoids  some  drawbacks  of  each  
approach  above.
§  Use  a  future:  T2  blocks  on  a  future  that  T1  sets.
§  None  of  the  above.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  3
Condition  Variables
Basic  idea:
§  T2  waits  on  a  condvar.
§  T1  notifys  condvar  when  A  done.
std::condition_variable  cv;  
std::mutex  m;  

…   …  
Do  A   std::unique_lock<std::mutex>  lk(m);  
cv.notify_one();   cv.wait(lk);  
…   …  

Task  T1
Task  T2

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  4
Condition  Variables
Three  problems:
§  Condvars  require  a  mutex,  but  there’s  no  state  to  protect.
Æ “Feels  wrong”

§  Condvars  may  receive  spurious  notifications.


Æ T2  must  verify  that  A  has  occurred.
w But  that’s  our  goal  with  the  condvar!
§  If  T1  notifys  before  T2  waits,  T2  blocks  forever!
Æ Must  check  to  see  if  A  has  happened  before  waiting.
w Again,  our  goal  is  to  use  the  condvar  for  that.
…   …  
Do  A   std::unique_lock<std::mutex>  lk(m);  
cv.notify_one();   cv.wait(lk);  
…   …  

Task  T1
Task  T2

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  5
std::atomic  Flags
Basic  idea:
§  T1  sets  a  flag  when  A  occurs.
§  T2  polls  flag  and  proceeds  only  when  flag  set.
std::atomic<bool>  flag(false);  

…   …  
Do  A   while  (!flag);  
flag  =  true;   …  
…  

Task  T1
Task  T2

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  6
std::atomic  Flags
Problem:
§  Polling  keeps  T2  running,  even  though  it’s  conceptually  blocked.
Æ Uses  CPU  (hence  power).
Æ Prevents  other  threads  from  using  its  HW  thread.
Æ Incurs  context  switch  overhead  each  time  it’s  scheduled.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  7
Condition  Variables  +  Flags
Basic  idea:
§  T2  waits  on  a  condvar,  checks  a  flag  when  notifyd.
§  T1  sets  a  flag  and  notifys  condvar  when  A  occurs.
std::condition_variable  cv;  
std::mutex  m;  
bool  flag;  
…   …  
Do  A   {  
{      std::unique_lock<std::mutex>  lk(m);  
   std::lock_guard<std::mutex>  g(m);      cv.wait(lk,  []{  return  flag;  });  
   flag  =  true;      …  
}   }  
cv.notify_one();   …  
…  

Task  T1
Task  T2

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  8
Condition  Variables  +  Flags
Problem:
§  Stilted  communication  protocol.
Æ T1  sets  flag  ⇒  A  has  occurred,  but  condvar  notify  still  necessary.
Æ T2  wakes  ⇒  A  has  probably  occurred,  but  flag  check  still  necessary.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  9
void  Futures
void  is  content-­‐‑free.  Its  availability  isn’t.  
§  Unlike  condvar:
Æ No  need  for  gratuitous  mutex.
Æ No  need  for  “did  event  already/really  occur?”  mechanism.

§  Unlike  std::atomic<bool>,  requires  no  polling.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  10
Example:  Starting  Thread  Suspended
Motivation:
§  Separate  thread  creation  from  running  something  on  it.
§  Available  on  some  platforms  (e.g.  Windows)
No  direct  support  in  C++11.
§  Easy  to  implement  via  void  future  (at  least  in  concept...).
§  Function  to  run  on  thread  still  required  at  thread  creation.
Æ “Function”  ≡  “Callable  object”

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  11
Starting  Thread  Suspended
 
std::promise<void>  p;  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  12
Poll  (Multi-­‐‑Choice  Single  Answer)
How  familiar  are  you  with  C++11’s  std::promises?
§  I’ve  wriMen  code  using  them.
§  I  haven’t  used  them,  but  I’ve  read  about  them.
§  I’ve  heard  of  them,  but  that’s  about  it.
§  I’ve  never  heard  of  them.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  13
Starting  Thread  Suspended
 
future
std::promise
 
std::promise<void>  p;   Caller
Callee

 
 
std::thread  t2([&p]{  p.get_future().wait();      //  start  t2  and  
                                       funcToRun();  }                        //  suspend  it  
                           );                                                            //  (conceptually)  
…                                                //  t2  is  "suspended"  waiting  
                                                 //  for  p  to  be  set  
p.set_value();                      //  t2  may  now  continue  
…  
t2.join();  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  14
std::thread  Destructor  Behavior
A  std::thread  object  may  be  joinable:
§  Represent  an  underlying  (i.e.,  OS)  thread  of  execution  (TOE).
Unjoinable  std::threads  represent  no  underlying  TOE.
§  Default-­‐‑constructed  std::threads.
§  std::threads  that  have  been  detached  or  moved  from.
§  std::threads  whose  TOE  has  been  joined.
§  Etc.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  15
std::thread  Destructor  Behavior
std::thread  dtor  calls  std::terminate  if  it’s  joinable:
{  
   std::thread  t(funcToRun);          //  t  is  joinable  
   …                                                          //  assume  t  remains  joinable  
}                                                              //  std::terminate  called  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  16
std::thread  Destructor  Behavior
Implication:  std::threads  must  be  unjoinable  on  all  paths  from  block:
§  Be  unjoinable:
Æ Be  joined  or
Æ Be  detached  or
Æ Be  otherwise  made  unjoinable  (e.g.,  moved  from)

§  All  paths:


Æ continue,  break,  goto,  return  
Æ Flow  off  end
Æ Exception

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  17
Unjoinable  on  All  Paths
All  paths  ⇒  RAII  classes.
§  None  for  std::thread  in  standard  library!
Æ From  the  Standard:
Either  implicitly  detaching  or  joining  a  joinable()  thread  in  its  
destructor  could  result  in  difficult  to  debug  correctness  (for  detach)  or  
performance  (for  join)  bugs  encountered  only  when  an  exception  is  
raised.
§  Easy  to  write  your  own.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  18
RAII  Class  for  std::thread  
class  ThreadRAII  {  
public:  
   enum  class  DtorAction  {  join,  detach  };    
   ThreadRAII(std::thread&&  t,  DtorAction  a)      //  in  dtor,  take  
   :  action(a),  t(std::move(t))  {}                          //  action  a  on  t  
   ~ThreadRAII()  
   {  
       if  (t.joinable())  {  
           if  (action  ==  DtorAction::join)  t.join();  
           else  t.detach();  
       }  
   }  
   std::thread&  get()  {  return  t;  }  
private:  
   DtorAction  action;  
   std::thread  t;  
};  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  19
RAII  Class  for  std::thread
ThreadRAII  t1(std::thread(doThisWork),                      //  join  on  
                           ThreadRAII::DtorAction::join);          //  destruction  
ThreadRAII  t2(std::thread(doThatWork),                      //  detach  on  
                           ThreadRAII::DtorAction::detach);      //  destruction  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  20
RAII  Class  for  std::thread
Dtor  prevents  generation  of  move  ops,  but  they  make  sense,  so:
class  ThreadRAII  {  
public:  
   enum  class  DtorAction  {  join,  detach  };                  //  as  before  
   ThreadRAII(std::thread&&  t,  DtorAction  a)              //  as  before  
   :  action(a),  t(std::move(t))  {}  
   ~ThreadRAII()  
   {  
       …  //  as  before  
   }  
   ThreadRAII(ThreadRAII&&)  =  default;                          //  support  
   ThreadRAII&  operator=(ThreadRAII&&)  =  default;    //  moving  
   std::thread&  get()  {  return  t;  }                                //  as  before  
private:                                                                                    //  as  before  
   DtorAction  action;  
   std::thread  t;  
};  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  21
Starting  Thread  Suspended
Use  of  ThreadRAII  ensures  join  is  always  performed:
std::promise<void>  p;                                                //  as  before  
std::thread  t2([&p]{  p.get_future().wait();    //  as  before  
                                       funcToRun();  }  
                           );                            
ThreadRAII  tr(std::move(t2),  ThreadRAII::DtorAction::join);  
…                                                                                        //  as  before  
 
p.set_value();                                                              //  as  before  
…  
t2.join();                                                                      //  no  longer  needed  
                                                                                           

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  22
Poll  (Multi-­‐‑Choice  Multi-­‐‑Answer)
Which  of  these  consequences  arise  from  using  ThreadRAII?
§  The  ThreadRAII  object  will  occupy  stack  space.  
§  ThreadRAII’s  ctor/dtor  will  reduce  the  speed  of  the  code.
§  The  code  is  exception  safe.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  23
Starting  Thread  Suspended
Use  of  ThreadRAII  ensures  join  is  always  performed:
std::promise<void>  p;                                                  //  as  before  
std::thread  t2([&p]{  p.get_future().wait();      //  as  before  
                                       funcToRun();  }  
                         );                            
ThreadRAII  tr(std::move(t2),  ThreadRAII::DtorAction::join);  
…                                                                        
p.set_value();                                          //  trouble  if  we  don’t  get  here  
                                                                     //  or  if  this  throws  
…  
t2.join();                                                  //  handled  by  ThreadRAII  dtor  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  24
When  A  Doesn’t  Occur
What  if  T1  needs  to  tell  T2  that  A  didn’t  occur  (e.g.,  due  to  an  exception)?
Task  T1
Task  T2

   

…   …  
Do  A   Block  until  A’s  done  
…   …  


§  Condvar  approach:  No  notify  ⇒  T2  blocks  forever  (modulo  spurious  
wakes).
§  Flag  approach:  Flag  never  set  ⇒  T2  polls  forever.
§  Condvar  +  Flag  approach:  No  notify  +  flag  never  set  ⇒  T2  blocks  forever  
(maybe  with  occasional  spurious  wakes).
Must  use  out-­‐‑of-­‐‑band  mechanism  to  communicate  “A  didn’t  occur”.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  25
When  A  Doesn’t  Occur
Observations:
§  Futures  can  hold  exceptions.
§  std::promise  dtor  puts  an  exception  into  an  unset  promise.
Ergo:
§  Use  local  std::promise  in  T1.
Æ It’s  set  ⇒  T2  “receives”  void.
Æ It’s  destroyed  w/o  being  set  ⇒  T2  receives  an  exception.

§  In  T2,  use  get  instead  of  wait.


Æ Allows  T2  to  determine  whether  A  occurred.
w Non-­‐‑exception:  it  did.
w Exception:  it  didn’t.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  26
Starting  Thread  Suspended
{                                                                                            //  make  p  local  
   std::promise<void>  p;  
   std::thread  t2([&p]  
                                 {  
                                     try  {  
                                         p.get_future().get();  
                                         funcToRun();                            //  A  occurred  
                                     }  
                                     catch(...)  {  
                                         …                                                  //  A  didn’t  occur  
                                     }  
                               );                            
   ThreadRAII  tr(std::move(t2),  ThreadRAII::DtorAction::join);  
   …                                                                        
   p.set_value();  
   …  
}                                                                            //  exception  written  to  p  
                                                                             //  if  it  hasn’t  been  set  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  27
Poll  (Multi-­‐‑Choice  Single  Answer)
Does  this  work,  i.e.,  ensure  that  T2  gets  an  exception  if  A  doesn’t  occur  in  T1?
§  Yes.
§  No.
§  Leave  me  alone—it’s  nearly  5AM  in  Sydney,  and  it’s  already  tomorrow!

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  28
Starting  Thread  Suspended
{  
   std::promise<void>  p;                                                //  created  first,  
                                                                                             //  destroyed  last  
   std::thread  t2([&p]  
                                 {  
                                     try  {  
                                         p.get_future().get();  
                                         funcToRun();  
                                     }  
                                     catch(...)  {  …  }  
                               );                            
   ThreadRAII  tr(std::move(t2),                                    //  created  after  
                               ThreadRAII::DtorAction::join);    //  p,  destroyed  
   …                                                                                          //  before  p  
   p.set_value();  
   …  
}                                                                      //  if  p  hasn’t  been  set,tr’s  
                                                                       //  dtor  hangs  on  a  join  

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  29
A  Clean,  Simple,  Straightforward,    
Non-­‐‑Error-­‐‑Prone  Solution
I  don’t  know  of  one.
§  I’m  sorry,  too.

Image:  “Depressed  puma?,”(c)  Tambako  The  Jaguar  @  Flickr.  License:  Creative  Commons  
AMribution-­‐‑NoDerivs  2.0  Generic.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  30
Costs  for  void  Futures
future
std::promise
 
Caller
Callee


Shared  State


future
Callee’s   std::promise
 
Caller

Result

Callee

 
Shared  state:
§  Dynamically  allocated ⇒  use  of  heap.
§  Reference-­‐‑counted  ⇒  atomic  increments/decrements.
Shared  State

future
RC
std::promise
 

Callee’s  
Result

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  31
One-­‐‑Shot  Event  Communication
Shared  state  may  be  wriMen  at  most  once.
§  Not  suitable  for  recurring  communication.
Alternatives  avoid  this  restriction:
§  Condition  variables.  
Æ May  be  notify-­‐‑ed  repeatedly.

§  std::atomics.  
Æ May  be  assigned  repeatedly.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  32
Guideline
Consider  void  futures  for  one-­‐‑shot  event  communication.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  33
Further  Information
Source  for  this  talk:
§  Effective  Modern  C++,  
ScoM  Meyers,  O’Reilly,  2015.
Æ Item  17:  Special  member  
function  generation.
Æ Item  37:  ThreadRAII.
Æ Item  38:  shared  state.
Æ Item  39:  void  futures.

§  Email  from  Tomasz  Kamiński.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  34
Further  Information
The  C++11  Concurrency  API:
§  The  C++  Standard  Library,  Second  Edition,    
Nicolai  M.  JosuMis,  Addison-­‐‑Wesley,  2012.  
 

§  C++  Concurrency  in  Action,  Anthony  Williams,  
Manning,  2012.  
 
 

§  “Why  would  I  want  to  start  a  thread  ‘suspended’?,”  Stack  Overflow,  
asked  1  July  2010.
§  “ThreadRAII  +  Thread  Suspension  =  Trouble?,”  ScoM  Meyers,  
The  View  from  Aristeia,  24  December  2013.
§  “std::futures  from  std::async  aren'ʹt  special!,”  ScoM  Meyers,  
The  View  from  Aristeia,  20  March  2013.

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  35
Licensing  Information
ScoM  Meyers  licenses  materials  for  this  and  other  training  courses  for  
commercial  or  personal  use.    Details:
§  Commercial  use: http://aristeia.com/Licensing/licensing.html
§  Personal  use:     http://aristeia.com/Licensing/personalUse.html
Courses  currently  available  for  personal  use  include:

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  36
About  Sco]  Meyers
ScoM  offers  training  and  consulting  services  on  
the  design  and  implementation  of  C++  software  
systems.  His  web  site,
http://aristeia.com/
provides  information  on:
§  Training  and  consulting  services
§  Books,  articles,  other  publications
§  Upcoming  presentations
§  Professional  activities  blog

ScoM  Meyers,  Software  Development  Consultant  http:// ©  2013-­‐‑14  ScoM  Meyers,  all  rights  reserved.  
aristeia.com/ Slide  37

You might also like