1 A batch of tasks to run
Or at least that’s how it started. Task was originally a std::function<void()>, which is essentially the interface of the thread pool I use. I realized, however, that I don’t actually need to restrict the interface quite that much. Thread takes a much wider range of things to run, and I can do the same thing. I have to forward the supplied callable and arguments into the lambda that the thread is running.
The key bit of code is
template <class Function, class... Args> void Batch::add(Function&& f, Args&&... args) { workers_.emplace_back([ this, f = std::forward<Function>(f), args... ]() { gate_.wait(); f(args...); }); }
There’s a lot of line noise in there, and it really looked simpler when it was just taking a std::function<void()>, but it’s not terrible. We take an object of type Function and a parameter pack of type Args by forwarding reference. That gets captured by the lambda, where we forward the function to the lambda, and capture the parameter pack. Inside the lambda we call the function with the pack, f(args). It’s probable that I should have used std::invoke there, which handles some of the more interesting cases of calling a thing with arguments. But this was sufficient unto the day. The captured this allows access to the gate_ variable the we’re waiting on. The workers_ are a vector of threads that we’ll later run run through and join() on, after open()ing the gate_.
void Batch::run() { gate_.open(); for (auto& thr : workers_) { thr.join(); } }
That’s really all there is to Batch. It’s a middle connective glue component. Does one thing, and tries to do it obviously well. That is important since I’m trying to build up test infrastructure, and testing the test infrastrucure is a hard problem.
I have reorganized the code repo in order to do some light testing, though.
2 GTest
# A directory to find Google Test sources. if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/googletest/CMakeLists.txt") add_subdirectory(googletest EXCLUDE_FROM_ALL) add_subdirectory(tests) else() message("GTEST Not Found at ${CMAKE_CURRENT_SOURCE_DIR}/googletest/CMakeLists.txt") endif()
This looks for googletest to be available, and if it is, add it to the project, and my tests subdirectory, otherwise issue a message. I prefer this to attempting to fix up the missing gtest automatically. That always seems to cause me problems, such as when I’m operating disconnected, on a train, like right now.
The tests I have are pretty simple, not much more than primitive breathing tests.
TEST_F(BatchTest, run1Test) { Batch batch; batch.add([this](){incrementCalled();}); EXPECT_EQ(0u, called); batch.run(); EXPECT_EQ(1u, called); }
or, to make sure that passing arguments worked
TEST_F(BatchTest, runArgTest) { Batch batch; int i = 0; batch.add([&i](int k){ i = k;}, 1); EXPECT_EQ(0, i); batch.run(); EXPECT_EQ(1, i); }
I don’t actually expect to find runtime errors with these tests. They exercise ths component just enough that I’m not generating compile errors in expected use cases. Template code can be tricky that way. Templates that aren’t instantiated can have horrible errors, but the compiler is willing to let them pass, if they mostly parse.
SFINAE may not be your friend.
3 Clang builds with current libc++
cmake -DCMAKE_INSTALL_PREFIX=~/install/llvm-master/ -DLLVM_ENABLE_LIBCXX=yes -DCMAKE_BUILD_TYPE=Release ../llvm/
Using that, at least from within cmake, is more complicated. Cmake has a strong bias towards using the system compiler. It also has a distinct problem with repeating builds.
NEVER edit your CMakeCache.txt. You can’t do anything with it. All the paths are hard coded. Always start over. Either keep the command line around, or create a cmake initial cache file, which isn’t the same thing at all as the CMakeCache.txt file.
Right now, I’m cargo-culting around code in my cmake files that checks if I’ve defined an LLVM_ROOT, and if I have supply the flags to ignore all the system files, and use the ones from the installed LLVM_ROOT, including some rpath fixup. There might be some way to convince cmake to do it, but there’s also only so much I will fight my metabuild system.
if(LLVM_ROOT) message(STATUS "LLVM Root: ${LLVM_ROOT}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++ -isystem ${LLVM_ROOT}/include/c++/v1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L ${LLVM_ROOT}/lib -l c++ -l c++abi") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,${LLVM_ROOT}/lib") else()
I only check for that if the compiler I’ve chosen is a clang compiler, and it’s not normally part of my environment.
4 Direction
For what I’m considering, look at Close Encounters of The Java Memory Model Kind
I want to be able to specify a state, with operations that mutate and observe the state. I want to be able to collect those observations in a deterministic way, which may require cooperation from the observers. I want to be able to collect the observations and report how many times each set of observations was obtained.
Something like:
class State { int x_; int y_; public: typedef std::tuple<int, int, int, int> Result; State() : x_(0), y_(0) {} void writer1() { y_ = 1; x_ = 1; } void reader1(Result& read) { std::get<0>(read) = x_; std::get<1>(read) = y_; } void reader2(Result& read) { std::get<2>(read) = x_; std::get<3>(read) = y_; } };
Running the writers and readers over different threads and observing the possible results. On some architectures, reader1 and reader2 can see entirely different orders, even though y_ will happen before x_, you might see x_ written and not y_.
What I’d eventually like to be able to do is say things like, “This function will only be evaluated once”, and have some evidence to back that up.
So the next step is something that will take a State and schedule all of the actions with appropriate parameters in a Batch, and produce the overall Result. Then something that will do that many many times, accumulating all of the results. And since this isn’t java, so we don’t have reflection techniques, the State class is going to have to cooperate a bit. The Result typedef is one way. It will also have to produce all of the actions that need to be batched, in some heterogenous form that I can then run.
Leave a Reply