From 3cf855ef6a733b1608b1c14fe8dbc833aac9d74d Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Sun, 27 Oct 2024 22:15:48 -0700 Subject: [PATCH 1/8] Core: started multi-threaded initialization. --- MMCore/MMCore.cpp | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 4bef95471..13ecc2042 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -59,7 +59,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -850,7 +852,10 @@ void CMMCore::initializeAllDevices() throw (CMMError) std::vector devices = deviceManager_->GetDeviceList(); LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices"; - for (size_t i=0; i, std::deque>> moduleMap; + + // first round, collect all DeviceAdapters + for (size_t i = 0; i < devices.size(); i++) { std::shared_ptr pDevice; try { @@ -860,12 +865,39 @@ void CMMCore::initializeAllDevices() throw (CMMError) logError(devices[i].c_str(), err.getMsg().c_str()); throw; } - mm::DeviceModuleLockGuard guard(pDevice); - LOG_INFO(coreLogger_) << "Will initialize device " << devices[i]; - pDevice->Initialize(); - LOG_INFO(coreLogger_) << "Did initialize device " << devices[i]; + std::shared_ptr pAdapter; + pAdapter = pDevice->GetAdapterModule(); - assignDefaultRole(pDevice); + + if (moduleMap.find(pAdapter) == moduleMap.end()) + { + std::deque> pDevices; + pDevices.push_back(pDevice); + moduleMap.insert({ pAdapter, pDevices }); + } + else + { + moduleMap.find(pAdapter)->second.push_back(pDevice); + } + } + + // second round, spin up threads to initialize devices, one thread per module + + std::map, std::deque>>::iterator it; + for (it = moduleMap.begin(); it != moduleMap.end(); it++) + { + // spin up thread here + std::deque> pDevices = it->second; + for (int i = 0; i < pDevices.size(); i++) { + std::shared_ptr pDevice = pDevices[i]; + + mm::DeviceModuleLockGuard guard(pDevice); + LOG_INFO(coreLogger_) << "Will initialize device " << devices[i]; + pDevice->Initialize(); + LOG_INFO(coreLogger_) << "Did initialize device " << devices[i]; + + assignDefaultRole(pDevice); + } } LOG_INFO(coreLogger_) << "Finished initializing " << devices.size() << " devices"; From ab6dea52bc18f8b79c445155a2f4e134e4879ecc Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 29 Oct 2024 12:54:44 -0700 Subject: [PATCH 2/8] Core: multi-threaded device initialization, one thread per device adpater. --- MMCore/MMCore.cpp | 54 ++++++++++++++++++++++++++++++----------------- MMCore/MMCore.h | 1 + 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 13ecc2042..c9ccf5e41 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -61,9 +61,11 @@ #include #include #include +#include #include #include #include +#include #include #ifdef _MSC_VER @@ -851,8 +853,8 @@ void CMMCore::initializeAllDevices() throw (CMMError) { std::vector devices = deviceManager_->GetDeviceList(); LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices"; - - std::map, std::deque>> moduleMap; + + std::map, std::deque, std::string>>> moduleMap; // first round, collect all DeviceAdapters for (size_t i = 0; i < devices.size(); i++) @@ -868,36 +870,30 @@ void CMMCore::initializeAllDevices() throw (CMMError) std::shared_ptr pAdapter; pAdapter = pDevice->GetAdapterModule(); - if (moduleMap.find(pAdapter) == moduleMap.end()) { - std::deque> pDevices; - pDevices.push_back(pDevice); + std::deque, std::string>> pDevices; + pDevices.push_back(make_pair(pDevice, devices[i])); moduleMap.insert({ pAdapter, pDevices }); } else { - moduleMap.find(pAdapter)->second.push_back(pDevice); + moduleMap.find(pAdapter)->second.push_back(make_pair(pDevice, devices[i])); } } // second round, spin up threads to initialize devices, one thread per module - std::map, std::deque>>::iterator it; + std::vector> futures; + std::map, std::deque, std::string>>>::iterator it; for (it = moduleMap.begin(); it != moduleMap.end(); it++) { - // spin up thread here - std::deque> pDevices = it->second; - for (int i = 0; i < pDevices.size(); i++) { - std::shared_ptr pDevice = pDevices[i]; - - mm::DeviceModuleLockGuard guard(pDevice); - LOG_INFO(coreLogger_) << "Will initialize device " << devices[i]; - pDevice->Initialize(); - LOG_INFO(coreLogger_) << "Did initialize device " << devices[i]; - - assignDefaultRole(pDevice); - } + auto f = std::async(std::launch::async, &CMMCore::initializeDequeOfDevices, this, it->second); + // std::future f = std::async(&CMMCore::initializeDequeOfDevices, *this, it->second, devices); + futures.push_back(std::move(f)); + } + for (auto& f : futures) { + f.get(); } LOG_INFO(coreLogger_) << "Finished initializing " << devices.size() << " devices"; @@ -905,6 +901,26 @@ void CMMCore::initializeAllDevices() throw (CMMError) updateCoreProperties(); } + +/** + * This function is executed by a single thread, allowing initializeAllDevices to operate multi-threaded. + * All devices are supposed to originate from the same device adapter + */ +int CMMCore::initializeDequeOfDevices(std::deque, std::string>> pDevices) { + for (int i = 0; i < pDevices.size(); i++) { + std::shared_ptr pDevice = pDevices[i].first; + + mm::DeviceModuleLockGuard guard(pDevice); + LOG_INFO(coreLogger_) << "Will initialize device " << pDevices[i].second; + pDevice->Initialize(); + LOG_INFO(coreLogger_) << "Did initialize device " << pDevices[i].second; + + // TODO: this may be better done synchronously + assignDefaultRole(pDevice); + } + return DEVICE_OK; +} + /** * Updates CoreProperties (currently all Core properties are * devices types) with the loaded hardware. diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index d06d86013..0d99f7eaf 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -696,6 +696,7 @@ class CMMCore void assignDefaultRole(std::shared_ptr pDev); void updateCoreProperty(const char* propName, MM::DeviceType devType) throw (CMMError); void loadSystemConfigurationImpl(const char* fileName) throw (CMMError); + int initializeDequeOfDevices(std::deque, std::string>> pDevices); }; #if defined(__GNUC__) && !defined(__clang__) From 7ca1fa802fb88d55eeef639a9a9a882bf2995c6e Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 29 Oct 2024 13:05:54 -0700 Subject: [PATCH 3/8] Core: multi-threaded device initialization, cleanup and assignDefaultRole synchronous. --- MMCore/MMCore.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index c9ccf5e41..f39b66098 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -846,7 +846,8 @@ void CMMCore::reset() throw (CMMError) /** * Calls Initialize() method for each loaded device. - * This method also initialized allowed values for core properties, based + * This implementation initializes devices on separate threads, one per device module (adapter). + * This method also initializes allowed values for core properties, based * on the collection of loaded devices. */ void CMMCore::initializeAllDevices() throw (CMMError) @@ -883,19 +884,28 @@ void CMMCore::initializeAllDevices() throw (CMMError) } // second round, spin up threads to initialize devices, one thread per module - std::vector> futures; std::map, std::deque, std::string>>>::iterator it; for (it = moduleMap.begin(); it != moduleMap.end(); it++) { auto f = std::async(std::launch::async, &CMMCore::initializeDequeOfDevices, this, it->second); - // std::future f = std::async(&CMMCore::initializeDequeOfDevices, *this, it->second, devices); futures.push_back(std::move(f)); } for (auto& f : futures) { + // Note: we can do a 'f.wait_for(std::chrono::seconds(20)' to wait up to 20 seconds before giving up + // which would avoid hanging with devices that hang in their initialize function f.get(); } + // assign default roles syncronously + for (it = moduleMap.begin(); it != moduleMap.end(); it++) + { + std::deque, std::string>> pDevices = it->second; + for (int i = 0; i < pDevices.size(); i++) + { + assignDefaultRole(pDevices[i].first); + } + } LOG_INFO(coreLogger_) << "Finished initializing " << devices.size() << " devices"; updateCoreProperties(); @@ -903,7 +913,7 @@ void CMMCore::initializeAllDevices() throw (CMMError) /** - * This function is executed by a single thread, allowing initializeAllDevices to operate multi-threaded. + * This helper function is executed by a single thread, allowing initializeAllDevices to operate multi-threaded. * All devices are supposed to originate from the same device adapter */ int CMMCore::initializeDequeOfDevices(std::deque, std::string>> pDevices) { @@ -914,9 +924,6 @@ int CMMCore::initializeDequeOfDevices(std::dequeInitialize(); LOG_INFO(coreLogger_) << "Did initialize device " << pDevices[i].second; - - // TODO: this may be better done synchronously - assignDefaultRole(pDevice); } return DEVICE_OK; } From 2cee9e0ce5ab5f7ecd04e83de36a2ff5b153b119 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Tue, 29 Oct 2024 13:08:18 -0700 Subject: [PATCH 4/8] Core: increment minor version as per Semver. --- MMCore/MMCore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index f39b66098..81bfd32f1 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -112,7 +112,7 @@ * (Keep the 3 numbers on one line to make it easier to look at diffs when * merging/rebasing.) */ -const int MMCore_versionMajor = 11, MMCore_versionMinor = 1, MMCore_versionPatch = 1; +const int MMCore_versionMajor = 11, MMCore_versionMinor = 2, MMCore_versionPatch = 1; /////////////////////////////////////////////////////////////////////////////// From f3dba346a4bee804a40405fff0c17015fc3d622f Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Wed, 30 Oct 2024 15:32:27 -0700 Subject: [PATCH 5/8] Core: Added ParallelDeviceInitialization feature set enabled by default. Setting it false will use the old code to do serial initialization. Also ensure that ports are initialized first. Needs testing on several systems before merging. --- MMCore/CoreFeatures.cpp | 6 +++ MMCore/CoreFeatures.h | 1 + MMCore/MMCore.cpp | 96 +++++++++++++++++++++++++++++++++++------ MMCore/MMCore.h | 2 + 4 files changed, 93 insertions(+), 12 deletions(-) diff --git a/MMCore/CoreFeatures.cpp b/MMCore/CoreFeatures.cpp index f05beb0ab..29f5525f0 100644 --- a/MMCore/CoreFeatures.cpp +++ b/MMCore/CoreFeatures.cpp @@ -112,6 +112,12 @@ const auto& featureMap() { // enabled perhaps a few years later. } }, + { + "ParallelDeviceInitialization", { + [] { return g_flags.ParallelDeviceInitialization; }, + [](bool e) { g_flags.ParallelDeviceInitialization = e; } + } + }, // How to add a new Core feature: see the comment at the top of this file. // Features (the string names) must never be removed once added! }; diff --git a/MMCore/CoreFeatures.h b/MMCore/CoreFeatures.h index 3cd04b19d..f1c180ca5 100644 --- a/MMCore/CoreFeatures.h +++ b/MMCore/CoreFeatures.h @@ -26,6 +26,7 @@ namespace features { struct Flags { bool strictInitializationChecks = false; + bool ParallelDeviceInitialization = true; // How to add a new Core feature: see the comment in the .cpp file. }; diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 81bfd32f1..95caa1062 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -200,6 +200,11 @@ CMMCore::~CMMCore() * attempted on a device that is not successfully initialized. When disabled, * no exception is thrown and a warning is logged (and the operation may * potentially cause incorrect behavior or a crash). + * - "ParallelDeviceInitialization" (default: enabled) When enabled, serial ports + * are initialized in serial order, and all other devices are in parallel, using + * multiple threads, one per device module. Early testing shows this to be + * reliable, but switch this off when issues are encountered during + * device initialization. * * Permanently enabled features: * - None so far. @@ -844,18 +849,70 @@ void CMMCore::reset() throw (CMMError) } +/** + * Calls Initialize() method for each loaded device. + * Parallel implemnetation should be faster + */ +void CMMCore::initializeAllDevices() throw (CMMError) +{ + if (this->isFeatureEnabled("ParallelDeviceInitialization")) + { + initializeAllDevicesParallel(); + } + else + { + initializeAllDevicesSerial(); + } +} + + +/** + * Calls Initialize() method for each loaded device. + * This method also initialized allowed values for core properties, based + * on the collection of loaded devices. + */ +void CMMCore::initializeAllDevicesSerial() throw (CMMError) +{ + std::vector devices = deviceManager_->GetDeviceList(); + LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices"; + + for (size_t i = 0; i < devices.size(); i++) + { + std::shared_ptr pDevice; + try { + pDevice = deviceManager_->GetDevice(devices[i]); + } + catch (CMMError& err) { + logError(devices[i].c_str(), err.getMsg().c_str()); + throw; + } + mm::DeviceModuleLockGuard guard(pDevice); + LOG_INFO(coreLogger_) << "Will initialize device " << devices[i]; + pDevice->Initialize(); + LOG_INFO(coreLogger_) << "Did initialize device " << devices[i]; + + assignDefaultRole(pDevice); + } + + LOG_INFO(coreLogger_) << "Finished initializing " << devices.size() << " devices"; + + updateCoreProperties(); +} + + /** * Calls Initialize() method for each loaded device. * This implementation initializes devices on separate threads, one per device module (adapter). * This method also initializes allowed values for core properties, based * on the collection of loaded devices. */ -void CMMCore::initializeAllDevices() throw (CMMError) +void CMMCore::initializeAllDevicesParallel() throw (CMMError) { std::vector devices = deviceManager_->GetDeviceList(); LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices"; std::map, std::deque, std::string>>> moduleMap; + std::vector> ports; // first round, collect all DeviceAdapters for (size_t i = 0; i < devices.size(); i++) @@ -868,22 +925,37 @@ void CMMCore::initializeAllDevices() throw (CMMError) logError(devices[i].c_str(), err.getMsg().c_str()); throw; } - std::shared_ptr pAdapter; - pAdapter = pDevice->GetAdapterModule(); - - if (moduleMap.find(pAdapter) == moduleMap.end()) + if (pDevice->GetType() == MM::SerialDevice) { - std::deque, std::string>> pDevices; - pDevices.push_back(make_pair(pDevice, devices[i])); - moduleMap.insert({ pAdapter, pDevices }); + ports.push_back(pDevice); } - else - { - moduleMap.find(pAdapter)->second.push_back(make_pair(pDevice, devices[i])); + else { + std::shared_ptr pAdapter; + pAdapter = pDevice->GetAdapterModule(); + + if (moduleMap.find(pAdapter) == moduleMap.end()) + { + std::deque, std::string>> pDevices; + pDevices.push_back(make_pair(pDevice, devices[i])); + moduleMap.insert({ pAdapter, pDevices }); + } + else + { + moduleMap.find(pAdapter)->second.push_back(make_pair(pDevice, devices[i])); + } } } - // second round, spin up threads to initialize devices, one thread per module + // Initialize ports first. This should be gast, so no need to go parallel (also could not hurt really) + for (std::shared_ptr pPort : ports) + { + mm::DeviceModuleLockGuard guard(pPort); + LOG_INFO(coreLogger_) << "Will initialize device " << pPort->GetLabel(); + pPort->Initialize(); + LOG_INFO(coreLogger_) << "Did initialize device " << pPort->GetLabel(); + } + + // second round, spin up threads to initialize non-port devices, one thread per module std::vector> futures; std::map, std::deque, std::string>>>::iterator it; for (it = moduleMap.begin(); it != moduleMap.end(); it++) diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index 0d99f7eaf..c8601a620 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -696,6 +696,8 @@ class CMMCore void assignDefaultRole(std::shared_ptr pDev); void updateCoreProperty(const char* propName, MM::DeviceType devType) throw (CMMError); void loadSystemConfigurationImpl(const char* fileName) throw (CMMError); + void initializeAllDevicesSerial() throw (CMMError); + void initializeAllDevicesParallel() throw (CMMError); int initializeDequeOfDevices(std::deque, std::string>> pDevices); }; From 4adff8fc2a244a777222e46958f9bc0fdf2cecc3 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Mon, 4 Nov 2024 11:25:14 -0800 Subject: [PATCH 6/8] Core-fast initialization: replaced deque with a vector (simpler), and clear the used vectors at the end of the function. I am not quite sure if this is needed, but after this change, I no longer saw an exception on Micro-Manager shutdown, so it may be helpful. --- MMCore/MMCore.cpp | 15 +++++++++------ MMCore/MMCore.h | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 95caa1062..161726a20 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -911,7 +911,7 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) std::vector devices = deviceManager_->GetDeviceList(); LOG_INFO(coreLogger_) << "Will initialize " << devices.size() << " devices"; - std::map, std::deque, std::string>>> moduleMap; + std::map, std::vector, std::string>>> moduleMap; std::vector> ports; // first round, collect all DeviceAdapters @@ -935,7 +935,7 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) if (moduleMap.find(pAdapter) == moduleMap.end()) { - std::deque, std::string>> pDevices; + std::vector, std::string>> pDevices; pDevices.push_back(make_pair(pDevice, devices[i])); moduleMap.insert({ pAdapter, pDevices }); } @@ -957,10 +957,10 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) // second round, spin up threads to initialize non-port devices, one thread per module std::vector> futures; - std::map, std::deque, std::string>>>::iterator it; + std::map, std::vector, std::string>>>::iterator it; for (it = moduleMap.begin(); it != moduleMap.end(); it++) { - auto f = std::async(std::launch::async, &CMMCore::initializeDequeOfDevices, this, it->second); + auto f = std::async(std::launch::async, &CMMCore::initializeVectorOfDevices, this, it->second); futures.push_back(std::move(f)); } for (auto& f : futures) { @@ -972,7 +972,7 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) // assign default roles syncronously for (it = moduleMap.begin(); it != moduleMap.end(); it++) { - std::deque, std::string>> pDevices = it->second; + std::vector, std::string>> pDevices = it->second; for (int i = 0; i < pDevices.size(); i++) { assignDefaultRole(pDevices[i].first); @@ -981,6 +981,9 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) LOG_INFO(coreLogger_) << "Finished initializing " << devices.size() << " devices"; updateCoreProperties(); + // not sure if this cleanup is needed, but should not hurt: + moduleMap.clear(); + ports.clear(); } @@ -988,7 +991,7 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) * This helper function is executed by a single thread, allowing initializeAllDevices to operate multi-threaded. * All devices are supposed to originate from the same device adapter */ -int CMMCore::initializeDequeOfDevices(std::deque, std::string>> pDevices) { +int CMMCore::initializeVectorOfDevices(std::vector, std::string>> pDevices) { for (int i = 0; i < pDevices.size(); i++) { std::shared_ptr pDevice = pDevices[i].first; diff --git a/MMCore/MMCore.h b/MMCore/MMCore.h index c8601a620..6889614e0 100644 --- a/MMCore/MMCore.h +++ b/MMCore/MMCore.h @@ -698,7 +698,7 @@ class CMMCore void loadSystemConfigurationImpl(const char* fileName) throw (CMMError); void initializeAllDevicesSerial() throw (CMMError); void initializeAllDevicesParallel() throw (CMMError); - int initializeDequeOfDevices(std::deque, std::string>> pDevices); + int initializeVectorOfDevices(std::vector, std::string>> pDevices); }; #if defined(__GNUC__) && !defined(__clang__) From a52a350c7d9fce9e9c2915a08fd2f4409b9e3d96 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Mon, 4 Nov 2024 14:27:50 -0800 Subject: [PATCH 7/8] Core: in parallel device initialization, catch first exception, initialize all other devices, ignoring more exceptions if they occur, then rethrow first exception. --- MMCore/MMCore.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index 161726a20..ef03027ee 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -963,10 +963,33 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) auto f = std::async(std::launch::async, &CMMCore::initializeVectorOfDevices, this, it->second); futures.push_back(std::move(f)); } - for (auto& f : futures) { - // Note: we can do a 'f.wait_for(std::chrono::seconds(20)' to wait up to 20 seconds before giving up + for (int i = 0; i < futures.size(); i++) { + // Note: we could do a 'f.wait_for(std::chrono::seconds(20)' to wait up to 20 seconds before giving up // which would avoid hanging with devices that hang in their initialize function - f.get(); + try + { + futures[i].get(); + } + catch (...) + { + std::exception_ptr pex = std::current_exception(); + // The std::future returned by std::async is special and its destructor blocks until the future completes. + // This is okay if there are 0 or 1 errors total(the successful initializations run to completion and the exception is propagated). + // When there are 2 or more errors, however, the second exception would be thrown in the destructor of the future, + // and throwing anything in a destructor is very bad(might terminate by default). + for (int j = i + 1; j < futures.size(); j++) + { + try + { + futures[j].get(); + } + catch (std::exception exj) { + // ignore these exceptions; + } + } + // Rethrow the first exception + std::rethrow_exception(pex); + } } // assign default roles syncronously From cc8b43844ac56ca6bbb90ef2a57188fd96f5d2c5 Mon Sep 17 00:00:00 2001 From: Nico Stuurman Date: Mon, 4 Nov 2024 15:14:20 -0800 Subject: [PATCH 8/8] Core: Fix typo in commment. --- MMCore/MMCore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMCore/MMCore.cpp b/MMCore/MMCore.cpp index ef03027ee..7735b5257 100644 --- a/MMCore/MMCore.cpp +++ b/MMCore/MMCore.cpp @@ -946,7 +946,7 @@ void CMMCore::initializeAllDevicesParallel() throw (CMMError) } } - // Initialize ports first. This should be gast, so no need to go parallel (also could not hurt really) + // Initialize ports first. This should be fast, so no need to go parallel (also could not hurt really) for (std::shared_ptr pPort : ports) { mm::DeviceModuleLockGuard guard(pPort);