ИУП: интенсив 7 дней + диагностический пакет (32 вопроса, 9 задач с автопроверкой)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
// Задача 06 — потокобезопасная очередь. Заполни реализацию.
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
class BlockingQueue {
|
||||
public:
|
||||
explicit BlockingQueue(size_t capacity) : cap_(capacity) {}
|
||||
~BlockingQueue() = default;
|
||||
|
||||
void push(int v) {
|
||||
(void)v;
|
||||
// TODO: должен блокироваться на полной очереди и бросать std::runtime_error после close()
|
||||
throw std::runtime_error("push: not implemented");
|
||||
}
|
||||
|
||||
bool pop(int& out) {
|
||||
(void)out;
|
||||
return false; // TODO
|
||||
}
|
||||
|
||||
void close() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
size_t size() const { return 0; }
|
||||
|
||||
private:
|
||||
size_t cap_;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
# Задача 06 — потокобезопасная ограниченная очередь (C++)
|
||||
|
||||
Классический вопрос на собеседовании в embedded/сетевую разработку: не «знаешь ли ты
|
||||
std::thread», а «умеешь ли ты не сломать счётчик под нагрузкой».
|
||||
|
||||
```c++
|
||||
class BlockingQueue {
|
||||
public:
|
||||
explicit BlockingQueue(size_t capacity);
|
||||
~BlockingQueue();
|
||||
void push(int v); // блокируется, пока очередь полна
|
||||
bool pop(int& out); // блокируется, пока пуста; false — если закрыта и пуста
|
||||
void close(); // после close: pop() опустошает остаток и отдаёт false
|
||||
size_t size() const;
|
||||
};
|
||||
```
|
||||
|
||||
Требования:
|
||||
- push после close() бросает `std::runtime_error`;
|
||||
- закрытие разблокирует все ждущие потоки (никакого вечного ожидания и busy-wait);
|
||||
- размер очереди никогда не превышает capacity;
|
||||
- ни одной гонки: тест собирается и гоняется с ThreadSanitizer (в том числе `size()`
|
||||
вызывается из другого потока — он тоже должен быть потокобезопасным).
|
||||
|
||||
Проверка: `python3 grade.py 06`. Критерий: все `ok`, TSan чистый, нет зависания.
|
||||
Разбор после сдачи: почему нужен predicate-цикл вокруг wait, и как один condition_variable
|
||||
на оба события даёт ложные пробуждения.
|
||||
@@ -0,0 +1,73 @@
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include "solution.cpp"
|
||||
|
||||
static int failures = 0;
|
||||
#define CHECK(cond, name) do { if (cond) std::printf("ok %s\n", name); \
|
||||
else { std::printf("FAIL %s (line %d)\n", name, __LINE__); ++failures; } } while (0)
|
||||
|
||||
int main() {
|
||||
// Базовая дисциплина
|
||||
{
|
||||
BlockingQueue q(2);
|
||||
q.push(1);
|
||||
q.push(2);
|
||||
CHECK(q.size() == 2, "size() == 2 после двух push");
|
||||
int v = 0;
|
||||
CHECK(q.pop(v) && v == 1, "pop -> 1 (FIFO)");
|
||||
CHECK(q.pop(v) && v == 2, "pop -> 2 (FIFO)");
|
||||
q.close();
|
||||
CHECK(!q.pop(v), "после close и опустошения pop -> false");
|
||||
bool threw = false;
|
||||
try { q.push(3); } catch (const std::runtime_error&) { threw = true; }
|
||||
CHECK(threw, "push после close -> std::runtime_error");
|
||||
}
|
||||
// Продюсеры/потребители под нагрузкой
|
||||
{
|
||||
const int P = 4, C = 4, PER = 20000, C3 = 64;
|
||||
BlockingQueue q(C3);
|
||||
std::atomic<int> pushed{0};
|
||||
std::atomic<long long> sum{0};
|
||||
std::atomic<int> popped{0};
|
||||
std::atomic<int> over{0};
|
||||
|
||||
auto producer = [&](int pid) {
|
||||
for (int i = 0; i < PER; ++i) {
|
||||
q.push(pid * 1000000 + i);
|
||||
pushed.fetch_add(1, std::memory_order_relaxed);
|
||||
if (q.size() > C3) over.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
auto consumer = [&](int) {
|
||||
int v = 0;
|
||||
while (q.pop(v)) {
|
||||
sum.fetch_add(v, std::memory_order_relaxed);
|
||||
popped.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::thread> ts;
|
||||
for (int i = 0; i < C; ++i) ts.emplace_back(consumer, i);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
for (int i = 0; i < P; ++i) ts.emplace_back(producer, i);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
while (pushed.load() < P * PER) std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
q.close();
|
||||
for (auto& t : ts) t.join();
|
||||
|
||||
long long expect = 0;
|
||||
for (int p = 0; p < P; ++p) for (int i = 0; i < PER; ++i) expect += (long long)p * 1000000 + i;
|
||||
std::printf(" (вставлено=%d, извлечено=%d, переполнений=%d)\n",
|
||||
pushed.load(), popped.load(), over.load());
|
||||
CHECK(pushed.load() == P * PER, "все вставки выполнены (нет потерь)");
|
||||
CHECK(popped.load() == P * PER, "извлечено ровно столько же (нет дублей/потерь)");
|
||||
CHECK(sum.load() == expect, "сумма значений совпадает с эталоном");
|
||||
CHECK(over.load() == 0, "size() не превышал capacity");
|
||||
}
|
||||
std::printf(failures ? "\nFAILURES: %d\n" : "\nALL PASS\n", failures);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user