Errata in second edition
- Page 14, listing 1.1
- There is a spurious "3" on the end of the 9th line. It should look like this:
std::thread t(hello);
Please Contact me if you spot any additional errors.
Errata in first edition
- Page 17, third code snippet
- The first line has an opening parenthesis following the
[]
lambda introducer rather than an opening brace. It should look like this:std::thread my_thread([]
({ do_something(); do_something_else(); }); - Pages 23/24, first paragraph of section 2.2 on page 23, last paragraph of page 23, whole of the first 3 paragraphs of page 24 and associated code snippet
This section was not revised after a late change to the C++11 standard draft, and is incorrect. If you attempt to pass a copy of an object to a function that takes a non-
const
reference, then the perfect forwarding semantics should cause the code to fail to compile. The text should be revised as follows:First paragraph of section 2.2 on page 23:
As shown in listing 2.4, passing arguments to the callable object or function is funda- mentally as simple as passing additional arguments to the
std::thread
constructor. But it's important to bear in mind that by default the arguments arecopied into inter- nal storage, where they can be accessed by the newly created thread of execution, and then passed to the callable object or function asrvalues as if they were temporaries. This is done even if the corresponding parameter in the function is expecting a reference. Here's a simple example:Last paragraph of page 23:
In this case, the problem is that you were relying on the implicit conversion of the pointer to the buffer into the
std::string
object expected as a function parameter, but this conversion happens too late because thestd::thread
constructor copies the supplied values as is, without converting to the expected argument type.Page 24:
It's
alsonot possible to get the reverse scenario: the object is copied, and what you wanted was a reference, since this won't compile.This might happenYou might try and do this if the thread is updating a data structure that's passed in by reference, for example:void update_data_for_widget(widget_id w,widget_data& data); #1 void oops_again(widget_id w) { widget_data data; std::thread t(update_data_for_widget,w,data); #2 display_status(); t.join(); process_widget_data(data);
#3}Although
update_data_for_widget
(#1) expects the second parameter to be passed by reference, thestd::thread
constructor (#2) doesn't know that; it's oblivious to the types of the arguments expected by the function and blindly copies the supplied values. However, the internal code passes copied argumentsas rvalues in order to work with move-only types, and will thus try to callupdate_data_for_widget
with an rvalue. This will therefore fail to compile as you can't pass an rvalue to a function expecting a non-const
reference.When it callsFor those of you familiar withupdate_data_for_widget
, it will end up passing a reference to the internal copy ofdata
and not a reference todata
itself. Consequently, when the thread finishes, these updates will be discarded as the internal copies of the supplied arguments are destroyed, andprocess_widget_data
will be passed an unchangeddata
(#3) rather than a correctly updated version.std::bind
, the solution will be readily apparent: you need to wrap the arguments that really need to be references instd::ref
. In this case, if you change the thread invocation tostd::thread t(update_data_for_widget,w,std::ref(data));
and then
update_data_for_widget
will be correctly passed a reference todata
rather than areference to atemporary copy ofdata
, and the code will now compile successfully.- Page 90, code snippet after 4th paragraph
- The duration type used for printing the time taken is incorrect
and won't compile. The use of
std::chrono::seconds
as the second template parameter is incorrect, and should be removed. The output statement should say:std::cout<<"do_something() took " <<std::chrono::duration<double
,std::chrono::seconds>(stop-start).count() <<" seconds"<<std::endl; - Page 120, listing 5.2
- The listing uses
std::milliseconds
for the timeout. The time periods are in namespacestd::chrono
, so this should bestd::chrono::milliseconds
:std::this_thread::sleep(std::chrono::milliseconds(1));
- Page 134, listing 5.8
- The annotation for mark 1 in
write_x_then_y()
belongs on mark 3 inread_y_then_x()
- Page 154, listing 6.2
- In the definition of
push()
, the value pushed on to the queue is of coursenew_value
, notdata
. The second line should therefore read:data_queue.push(std::move(new_value
data)); - Page 244, listing 8.2
- The line indicated by the number 9 cueball is missing template
parameters for
accumulate_block
. The line should read:accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]);
- Page 246, listing 8.3
- The line indicated by the number 7 cueball is missing template
parameters for
accumulate_block
. The line should read:T last_result=accumulate_block<Iterator,T>()(block_start,last);
- Page 247, code snippet
- There are missing template parameters for
accumulate_block
after thefor
loop. The line should read:T last_result=accumulate_block<Iterator,T>()(block_start,last);
- Page 249, listing 8.4
- There are missing template parameters for the direct call to
accumulate_block
on the 4th line of the listing on this page. The line should read:T last_result=accumulate_block<Iterator,T>()(block_start,last);
- Page 265, listing 8.11
- There is a test for an empty range that returns
last
. However, this function has avoid
return type, so it should just be a plainreturn
:if(!length) return
last; - Page 282, listing 9.5
- In the
while
loop that waits for thenew_lower
result to be ready, the loop condition has a spurious!
, which should be removed:while(
!new_lower.wait_for(std::chrono::seconds(0))==std::future_status::timeout) - Page 287, listing 9.8
- In the
thread_pool
constructor, thefor
loop that creates the queues and starts the threads needs to be split into two loops in order to avoid a data race on the queues as threads try and steal work from each other. The first should create the queues, the second create the threads.for(unsigned i=0;i<thread_count;++i) { queues.push_back(std::unique_ptr<work_stealing_queue>( new work_stealing_queue)); } for(unsigned i=0;i<thread_count;++i) { threads.push_back( std::thread(&thread_pool::worker_thread,this,i)); }