diff --git a/apps/fsearch/fsearch.cpp b/apps/fsearch/fsearch.cpp
index 118253b0b902288100f6b52efefd9c9b9a8add8a..8ef73c0adb1eca949897986737df94fce9acfdfe 100644
--- a/apps/fsearch/fsearch.cpp
+++ b/apps/fsearch/fsearch.cpp
@@ -15,6 +15,7 @@
 #include "CountingPrivateSemaphore.hpp"
 #include "Fiber.hpp"
 #include "Runtime.hpp"
+#include "Semaphore.hpp"
 #include "emper.hpp"
 #include "io.hpp"
 
@@ -25,7 +26,13 @@ namespace fs = std::filesystem;
 const char* needle;
 size_t needle_len;
 
+emper::Semaphore* max_running;
+
 void search(const std::string& path) {
+	if (max_running) {
+		max_running->acquire();
+	}
+
 	int fd = emper::io::openAndWait(path.c_str(), O_RDONLY);
 	if (fd < 0) {
 		DIE_MSG_ERRNO("open failed");
@@ -50,6 +57,10 @@ void search(const std::string& path) {
 	}
 
 out:
+	if (max_running) {
+		max_running->release();
+	}
+
 	emper::io::closeAndForget(fd);
 }
 
@@ -67,8 +78,12 @@ void walk_dir() {
 
 auto main(int argc, char* argv[]) -> int {
 	if (argc < 2) {
-		std::cerr << "Usage: " << argv[0] << " <needle>" << std::endl;
-		return EXIT_FAILURE;
+		std::cerr << "Usage: " << argv[0] << " <needle> [max fibers]" << std::endl;
+		return EXIT_SUCCESS;
+	}
+
+	if (argc == 3) {
+		max_running = new emper::Semaphore(std::stoi(argv[2]));
 	}
 
 	needle = argv[1];
@@ -80,4 +95,5 @@ auto main(int argc, char* argv[]) -> int {
 	runtime.scheduleFromAnywhere(*dirWalker);
 
 	runtime.waitUntilFinished();
+	return EXIT_SUCCESS;
 }
diff --git a/emper/Semaphore.hpp b/emper/Semaphore.hpp
index 0ec2340e90ea931b2fdf15b6bf705c4b5d93b429..d68919f4dd5e68abaf1d3af245c46eb027af6354 100644
--- a/emper/Semaphore.hpp
+++ b/emper/Semaphore.hpp
@@ -17,6 +17,9 @@ class Semaphore {
 	std::mutex mutex;
 
  public:
+	Semaphore() = default;
+	Semaphore(unsigned int count) : count(count){};
+
 	auto acquire() -> bool {
 		bool blocked;
 		mutex.lock();