C++ Concurrency in Action: Practical Multithreading
by Anthony Williams

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 are copied 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 as rvalues 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 the std::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, the std::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 arguments as rvalues in order to work with move-only types, and will thus try to call update_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 calls update_data_for_widget, it will end up passing a reference to the internal copy of data and not a reference to data itself. Consequently, when the thread finishes, these updates will be discarded as the internal copies of the supplied arguments are destroyed, and process_widget_data will be passed an unchanged data (#3) rather than a correctly updated version. For those of you familiar with std::bind, the solution will be readily apparent: you need to wrap the arguments that really need to be references in std::ref. In this case, if you change the thread invocation to

  std::thread t(update_data_for_widget,w,std::ref(data));

and then update_data_for_widget will be correctly passed a reference to data rather than a reference to atemporary copy of data, 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 namespace std::chrono, so this should be std::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 in read_y_then_x()
Page 154, listing 6.2
In the definition of push(), the value pushed on to the queue is of course new_value, not data. The second line should therefore read:
data_queue.push(std::move(new_valuedata));
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 the for 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 a void return type, so it should just be a plain return:
if(!length)
    return last;
Page 282, listing 9.5
In the while loop that waits for the new_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, the for 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));
}