diff --git a/libs/main/thread/piprotectedvariable.h b/libs/main/thread/piprotectedvariable.h
index 5390fb5d..754eb4e3 100644
--- a/libs/main/thread/piprotectedvariable.h
+++ b/libs/main/thread/piprotectedvariable.h
@@ -4,22 +4,22 @@
//! \~english Thread-safe variable
//! \~russian Потокобезопасная переменная
/*
- PIP - Platform Independent Primitives
- Thread-safe variable
- Ivan Pelipenko peri4ko@yandex.ru
+ PIP - Platform Independent Primitives
+ Thread-safe variable
+ Ivan Pelipenko peri4ko@yandex.ru
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License
- along with this program. If not, see .
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
*/
#ifndef PIPROTECTEDVARIABLE_H
@@ -34,21 +34,29 @@
template
class PIP_EXPORT PIProtectedVariable {
public:
+ //! \~english Constructs %PIProtectedVariable and initialize variable by value `v`.
+ //! \~russian Создает %PIProtectedVariable и инициализирует переменную значением `v`.
+ PIProtectedVariable(T v = T()): var(std::move(v)) {}
+
//! \~\brief
//! \~english Pointer-like wrapper returned by \a getRef() while the protected value remains locked.
//! \~russian Указателеподобная обертка, возвращаемая \a getRef(), пока защищенное значение остается заблокированным.
class PIP_EXPORT Pointer {
friend class PIProtectedVariable;
+ NO_COPY_CLASS(Pointer);
+ Pointer & operator=(Pointer && other) = delete;
public:
- //! \~english Copies wrapper state for access to the same protected value.
- //! \~russian Копирует состояние обертки для доступа к тому же защищенному значению.
- Pointer(const Pointer & v): pv(v.pv), counter(v.counter + 1) {}
+ //! \~english Move constructor - transfers ownership of the lock.
+ //! \~russian Конструктор перемещения - передает владение блокировкой.
+ Pointer(Pointer && other): pv(other.pv) { other.can_unlock = false; };
- //! \~english Destroys wrapper and releases the mutex when it owns the original lock.
- //! \~russian Уничтожает обертку и освобождает мьютекс, когда она владеет исходной блокировкой.
+ //! \~english Destroys wrapper and releases the mutex.
+ //! \~russian Уничтожает обертку и освобождает мьютекс.
~Pointer() {
- if (counter == 0) pv.mutex.unlock();
+ if (can_unlock) {
+ pv.mutex.unlock();
+ }
}
//! \~english Returns pointer access to the protected value.
@@ -60,11 +68,11 @@ public:
T & operator*() { return pv.var; }
private:
- Pointer() = delete;
- Pointer(PIProtectedVariable & v): pv(v) {}
+ explicit Pointer() = delete;
+ explicit Pointer(PIProtectedVariable & v): pv(v) { pv.mutex.lock(); }
PIProtectedVariable & pv;
- int counter = 0;
+ bool can_unlock = true;
};
//! \~english Replaces the protected value with \a v.
@@ -76,10 +84,7 @@ public:
//! \~english Locks the value and returns wrapper-based access to it.
//! \~russian Блокирует значение и возвращает обертку для доступа к нему.
- Pointer getRef() {
- mutex.lock();
- return Pointer(*this);
- }
+ Pointer getRef() { return Pointer(*this); }
//! \~english Returns a copy of the protected value.
//! \~russian Возвращает копию защищенного значения.
@@ -98,7 +103,7 @@ public:
private:
mutable PIMutex mutex;
- T var;
+ T var = {};
};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index a5ba75e2..67f7d5f5 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -37,3 +37,4 @@ pip_test(piobject)
pip_test(client_server pip_client_server)
pip_test(io)
pip_test(system)
+pip_test(thread)
diff --git a/tests/thread/piprotectedvariable_test.cpp b/tests/thread/piprotectedvariable_test.cpp
new file mode 100644
index 00000000..e7facc40
--- /dev/null
+++ b/tests/thread/piprotectedvariable_test.cpp
@@ -0,0 +1,118 @@
+#include "piprotectedvariable.h"
+#include "pistring.h"
+#include "pithread.h"
+#include "piliterals_time.h"
+
+#include "gtest/gtest.h"
+#include
+
+// Basic functionality tests
+TEST(PIProtectedVariable_Basic, BasicFunctionality) {
+ // Test basic set/get with different types
+ PIProtectedVariable pvInt;
+ PIProtectedVariable pvDouble;
+ PIProtectedVariable pvString;
+
+ pvInt.set(123);
+ pvDouble.set(3.14159);
+ pvString.set(PIString("Hello, World!"));
+
+ EXPECT_EQ(pvInt.get(), 123);
+ EXPECT_DOUBLE_EQ(pvDouble.get(), 3.14159);
+ EXPECT_EQ(pvString.get(), PIString("Hello, World!"));
+
+ // Test operator=
+ pvInt = 999;
+ EXPECT_EQ(pvInt.get(), 999);
+
+ // Test getRef() with Pointer
+ struct TestStruct {
+ int x = 10;
+ int y = 20;
+ int getValue() const { return x + y; }
+ };
+
+ PIProtectedVariable pvStruct;
+
+ auto ptr = pvStruct.getRef();
+ EXPECT_EQ(ptr->x, 10);
+ EXPECT_EQ(ptr->y, 20);
+ EXPECT_EQ(ptr->getValue(), 30);
+
+ // Modify through pointer
+ *ptr = TestStruct();
+ ptr->x = 100;
+ EXPECT_EQ(pvStruct.get().x, 100);
+
+ // Test for Pointer
+ pvInt.set(42);
+ auto ptr1 = pvInt.getRef();
+ EXPECT_EQ(*ptr1, 42);
+ *ptr1 = 55;
+ EXPECT_EQ(*ptr1, 55);
+ auto ptr2 = std::move(ptr1);
+ EXPECT_EQ(*ptr2, 55);
+ *ptr2 = 100;
+ EXPECT_EQ(*ptr2, 100);
+ auto ptr3 = pvInt.getRef();
+ EXPECT_EQ(*ptr3, 100);
+ *ptr3 = 333;
+ EXPECT_EQ(*ptr3, 333);
+ EXPECT_EQ(pvInt.get(), 333);
+}
+
+// Thread safety tests
+TEST(PIProtectedVariable_ThreadSafety, ConcurrentReadWrite) {
+ PIProtectedVariable pv;
+
+ std::atomic writeCount(0);
+ std::atomic readCount(0);
+ std::atomic invalidReads(0);
+ const int NUM_ITERATIONS = 1000;
+ const int NUM_WRITERS = 10;
+ const int NUM_READERS = 20;
+ const int TOTAL_WRITES = NUM_WRITERS * NUM_ITERATIONS;
+
+ // Collect thread handles for joining
+ PIVector threads;
+
+ // Create writer threads
+ for (int i = 0; i < NUM_WRITERS; ++i) {
+ threads.push_back(new PIThread([&pv, &writeCount]() {
+ for (int j = 0; j < NUM_ITERATIONS; ++j) {
+ auto val = pv.getRef();
+ (*val)++;
+ auto val2 = pv.getRef();
+ writeCount++;
+ ASSERT_EQ(writeCount, *val2);
+ }
+ }));
+ }
+
+ // Create reader threads
+ for (int i = 0; i < NUM_READERS; ++i) {
+ threads.push_back(new PIThread([&pv, &invalidReads, &readCount]() {
+ for (int j = 0; j < NUM_ITERATIONS; ++j) {
+ auto val = pv.get();
+ readCount++;
+ // Value should always be in valid range [0, TOTAL_WRITES]
+ if (val < 0 || val > TOTAL_WRITES) {
+ invalidReads++;
+ }
+ }
+ }));
+ }
+
+ threads.forEach([](PIThread * & t) {t->startOnce();});
+ threads.forEach([](PIThread * & t) {t->waitForFinish(2_s);});
+ piDeleteAll(threads);
+
+ // Verify results
+ EXPECT_EQ(writeCount, TOTAL_WRITES);
+ EXPECT_EQ(readCount, NUM_READERS * NUM_ITERATIONS);
+ EXPECT_EQ(invalidReads, 0) << "All reads should return valid values in range [0, " << TOTAL_WRITES << "]";
+
+ // Final value should be TOTAL_WRITES
+ int finalVal = pv.get();
+ EXPECT_EQ(finalVal, TOTAL_WRITES);
+}