Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.71/21: Рейтинг темы: голосов - 21, средняя оценка - 4.71
13 / 13 / 1
Регистрация: 19.10.2019
Сообщений: 607

Spinlock c std::this_thread::yield

01.01.2020, 16:17. Показов 5094. Ответов 30
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Добрый день, не могу найти обоснование применения функции "уступить другим потокам" при реализации спинлока, хотя нечто подобное есть в релизации boost::detail::spinlock. Я набрался наглости сделать свою реализацию, потомучто моя интуиция подсказывает что она лучше. Но надо ещё всё это обосновать, а я даже не знаю откуда взять информацию. Собственно надо обосновать что после некоторого количества "долбёжек" уместно сделать паузу, и ещё лучше обосновать предел после которого надо делать паузу. Выбранный предел в 100'000 наверняка неоптимальный.
Или не стоит замарачиваться а взять boost::detail::spinlock, но он нормальный в плане производительности или это просто игрушка детская ?

Вот моя реализация для случая когда спинлок требуется для синглтона:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class TSpinlock
{
    static std::atomic_bool spin_flag; // noexcept
    static std::atomic_uint mesaured_delay;
public:
    static void  mesaure_spinlockratio()
    {
        try
        {
            using std::this_thread::yield;
            using ratio = std::chrono::nanoseconds;
            using std::chrono::duration_cast;
            using clock = std::chrono::high_resolution_clock;
            unsigned curr_delay = 0;
            auto start = clock::now();
            std::unique_lock<std::mutex> lock(mtxwd);
            lock.unlock();
            auto stop = clock::now();
            auto dur = duration_cast<ratio>(stop - start);
            curr_delay = dur.count();
            std::cout << "mutex delay(nano):" << curr_delay << std::endl;
            bool cond = (curr_delay > (default_delay));
            mesaured_delay =  cond?curr_delay: default_delay;
        }
        catch(...)
        {
            assert("mesaure_spinlockratio");
        }
    }
 
    static void lock() noexcept
    {
        using std::this_thread::yield; // noexcept
        using ratio =std::chrono::nanoseconds;
        using std::chrono::duration_cast;
        using clock = std::chrono::high_resolution_clock;
        constexpr auto max_attempts = 100'000;
        unsigned curr_ratio = 1;
        unsigned timedelay = mesaured_delay.load();
        bool result = false;
        unsigned attempt_number = 0;
        while(!result)
        {
            bool expected = false;
            result = spin_flag.compare_exchange_weak(expected, true); // noexcept
            if (!result)
            {
                attempt_number++;
                if (attempt_number == max_attempts)
                {
                    attempt_number = 0;
                    unsigned curr_delay = 0;
                    auto start = clock::now(); // noexcept
                    do
                    {
                        yield(); // noexcept
                        auto stop = clock::now(); // noexcept
                        // constexpr
                        curr_delay = duration_cast<ratio>(stop - start).count();
                    } while(curr_delay < timedelay);
 
                }
            }
        }
    }
 
    static void unlock() noexcept
    {
        spin_flag.store(false);
    }
};
std::atomic_bool TSpinlock::spin_flag = false; // noexcept
std::atomic_uint TSpinlock::mesaured_delay = default_delay;
Добавлено через 6 минут
Очень много радикальных суждений встречал по поводу спинлока и хочется написать код, который будет воспринят нормально другими разработчиками.

Добавлено через 30 минут
Небольшие огрешности были. Поправил.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class TSpinlock
{
    static std::atomic_bool spin_flag; // noexcept
    static std::atomic_uint mesaured_delay;
    static std::mutex mtxwd;
    static constexpr unsigned default_delay = 10;  // minumum for spinlock
public:
    static void  mesaure_spinlockratio()
    {
        try
        {
            using std::this_thread::yield;
            using ratio = std::chrono::nanoseconds;
            using std::chrono::duration_cast;
            using clock = std::chrono::high_resolution_clock;
            unsigned curr_delay = 0;
            auto start = clock::now();
            std::unique_lock<std::mutex> lock(mtxwd);
            lock.unlock();
            auto stop = clock::now();
            auto dur = duration_cast<ratio>(stop - start);
            curr_delay = dur.count();
            std::cout << "mutex delay(nano):" << curr_delay << std::endl;
            bool cond = (curr_delay > (default_delay));
            mesaured_delay =  cond?curr_delay: default_delay;
        }
        catch(...)
        {
            assert("mesaure_spinlockratio");
        }
    }
 
    static void lock() noexcept
    {
        using std::this_thread::yield; // noexcept
        using ratio =std::chrono::nanoseconds;
        using std::chrono::duration_cast;
        using clock = std::chrono::high_resolution_clock;
        constexpr auto max_attempts = 100'000;
        unsigned curr_ratio = 1;
        unsigned timedelay = mesaured_delay.load();
        bool result = false;
        unsigned attempt_number = 0;
        while(!result)
        {
            bool expected = false;
            result = spin_flag.compare_exchange_weak(expected, true); // noexcept
            if (!result)
            {
                attempt_number++;
                if (attempt_number == max_attempts)
                {
                    attempt_number = 0;
                    unsigned curr_delay = 0;
                    auto start = clock::now(); // noexcept
                    do
                    {
                        yield(); // noexcept
                        auto stop = clock::now(); // noexcept
                        // constexpr
                        curr_delay = duration_cast<ratio>(stop - start).count();
                    } while(curr_delay < timedelay);
                }
            }
        }
    }
 
    static void unlock() noexcept
    {
        spin_flag.store(false);
    }
};
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
01.01.2020, 16:17
Ответы с готовыми решениями:

С++ memory ordering: fetch_sub(acquire) и spinlock на основе atomic_flag (Энтони Вильямс "Мультитрид в действии")
Читаю Вильямса по мультитриду. 1) В книге приведён пример класса spinlock на основе atomic_flag: class spinlock_mutex{ ...

Не могу разобраться как обновить в std::map<std::string, вектор_структур>
Не могу разобраться как обновить вектор структур после его добавления в map без удаления и перезаписи struct pStruct { int...

std::string, std::fstream, ошибка кучи
где то начало вылетать при операции += с локальной переменной std::string. Заменил на свой qString. Замечательно, то же самое... ошибка при...

30
Любитель чаепитий
 Аватар для GbaLog-
3745 / 1801 / 566
Регистрация: 24.08.2014
Сообщений: 6,020
Записей в блоге: 1
08.01.2020, 10:59
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от hoggy Посмотреть сообщение
расскажи, что за ситуации такие.
ну, об этом уже сказано выше. блокировка спинлока должна быть короткой. иначе это вызовет большую трату ресурсов процессора.
к тому же спинлок нужен только там, где время блокировки/разблокировки имеет какую-то важность, то есть в высокопроизводительных системах. в прикладных задачах нет никакого смысла использовать их, т.к. саму их необходимость надо доказывать путём бенчмарков, иначе они могут оказаться даже дороже обычного mutex'a.
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
09.01.2020, 11:36
Цитата Сообщение от GbaLog- Посмотреть сообщение
блокировка спинлока должна быть короткой. иначе это вызовет большую трату ресурсов процессора.
видимо ты не совсем правильно понял суть бенчмарка.
здесь речь идет о скорости выполнения работы программы.

Цитата Сообщение от hoggy Посмотреть сообщение
выполнил бенчмарки:
сравнительные тесты между spinlock, std::mutex и std::recursieve_mutex.
оказалось, что стандартные мутексы работают примерно с одинаковой скоростью...
самым медленным же оказался spinlock.
насколько сильно при этом будет загружен ЦП - не важно.

"медленные" мьютексы противопоставляются "быстрым" спинлокам.
цель бенчмарка - проиллюстрировать:
что с "быстрым" спинлоком программа начнет работать хотя бы чуточку,
но явно быстрее.

сам бенчмарк я запускал на очень мощной машине.
для ЦП нагрузка - камаринный укус.

бенчмарк показал:
спинлок с квадратными колесами лишь замедлил работу программы.

Добавлено через 3 минуты
Цитата Сообщение от GbaLog- Посмотреть сообщение
в прикладных задачах нет никакого смысла использовать их, т.к. саму их необходимость надо доказывать путём бенчмарков, иначе они могут оказаться даже дороже обычного mutex'a.
вот что значит "дороже"?
процессор зазря греть будет?

счет за электричество будет больше, или как?

или ты имеешь ввиду,
что спинлок в какой нибудь одно-ядерной системе не даст нормально работать другим процессам/тредам,
что в целом приведет к замедлению работы всей системы?
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
09.01.2020, 11:48
Цитата Сообщение от hoggy Посмотреть сообщение
или ты имеешь ввиду,
что спинлок в какой нибудь одно-ядерной системе не даст нормально работать другим процессам/тредам,
что в целом приведет к замедлению работы всей системы?
Это само-собой. Спинлок эффективен, когда потоки выполняются на разных процессорах/ядрах.
Покажи код своего бенчмарка.

Добавлено через 8 минут
Я в своё время делал параллельную сортировку массива, на базе алгоритма быстой сортировки. При использовании спинлока, на std::atomic_flag, она работала быстрее, чем при использовании std::mutex.
0
2737 / 894 / 331
Регистрация: 10.02.2018
Сообщений: 2,136
09.01.2020, 14:25
Типа тест
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <iostream>
#include <thread>
#include <chrono>
 
using namespace std;
using namespace chrono;
 
volatile int globalCount;
 
inline void lock();
inline void unlock();
 
#define INC_DEC_COUNT 1000000
 
// в цикле инкриментируем глобальную переменную
void test1()
{
    for (int i = 0; i < INC_DEC_COUNT; i++)
    {
        lock();
        globalCount++;
        unlock();
    }
}
 
// в цикле декриментируем глобальную переменную
void test2()
{
    for (int i = 0; i < INC_DEC_COUNT; i++)
    {
        lock();
        globalCount--;
        unlock();
    }
}
 
int main()
{
    // проводим тест 10 раз
    for (int test = 0; test < 10; test++)
    {
        // время начала
        auto start = high_resolution_clock::now();
 
        // запускаем два потока
        thread th1(test1);
        thread th2(test2);
 
        // ждём завершения потоков
        th1.join();
        th2.join();
 
        // время окончания
        auto finish = high_resolution_clock::now();
        auto dur = duration_cast<microseconds>(finish - start);
 
        // вывод времени исполнения в микро секундах
        cout << test << ") " << dur.count() << " us" << endl;
    }
 
    cout << "globalCount=" << globalCount << endl;
 
    return 0;
}
 
// если оставить SPINLOCK, то тестируется блокировка спинлоком                           время работы 280 мс  (1 * X)
// если оставить STDMUTEX, то тестируется блокировка std-мьютексом                       время работы 590 мс  (2 * X)
// если оставить WINMUTEX, то тестируется блокировка windows-мьютексом                   время работы 6500 мс (23 * X)
// если убрать всё, то тестируется без блокировки                                        время работы 3 мс    (X / 100)
#define SPINLOCK
//#define STDMUTEX
//#define WINMUTEX
 
 
 
#ifdef SPINLOCK
 
#include <atomic>
atomic_flag locker;
 
void lock()
{
    while (locker.test_and_set())
        this_thread::yield();
}
 
void unlock()
{
    locker.clear();
}
 
#else
 
#ifdef STDMUTEX
 
#include <mutex>
mutex locker;
 
void lock()
{
    locker.lock();
}
 
void unlock()
{
    locker.unlock();
}
#else
 
#ifdef WINMUTEX
 
#include <Windows.h>
HANDLE locker = CreateMutex(0, FALSE, 0);
 
void lock()
{
    WaitForSingleObject(locker, INFINITE);
}
 
void unlock()
{
    ReleaseMutex(locker);
}
 
#else
 
void lock()
{
}
 
void unlock()
{
}
 
#endif
 
#endif

Примерное время работы теста на моём компе в миллисекундах:
спинлок28X
std-мьютекс59020*X
windows-мьютекс6500230*X
без блокировок30.1*X
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
09.01.2020, 14:29
Ygg,
C++
1
atomic_flag locker = ATOMIC_FLAG_INIT;
0
285 / 176 / 21
Регистрация: 16.02.2018
Сообщений: 666
09.01.2020, 14:34
Ygg, а если спинлок с std::memory_order_acquire/std::memory_order_release захватывать/отпускать?
1
фрилансер
 Аватар для Алексей1153
6504 / 5748 / 1134
Регистрация: 11.10.2019
Сообщений: 15,392
09.01.2020, 14:40
Ygg, ещё виндовый CRITICAL_SECTION пропущен )
0
2737 / 894 / 331
Регистрация: 10.02.2018
Сообщений: 2,136
09.01.2020, 15:01
Короче, напутал всё во временах. Ещё раз.
Типа тест №2
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <iostream>
#include <thread>
#include <chrono>
 
using namespace std;
using namespace chrono;
 
volatile int globalCount;
 
inline void init();
inline void lock();
inline void unlock();
 
#define INC_DEC_COUNT 1000000
 
// в цикле инкриментируем глобальную переменную
void test1()
{
    for (int i = 0; i < INC_DEC_COUNT; i++)
    {
        lock();
        globalCount++;
        unlock();
    }
}
 
// в цикле декриментируем глобальную переменную
void test2()
{
    for (int i = 0; i < INC_DEC_COUNT; i++)
    {
        lock();
        globalCount--;
        unlock();
    }
}
 
int main()
{
    init();
 
    // проводим тест 10 раз
    for (int test = 0; test < 10; test++)
    {
        // время начала
        auto start = high_resolution_clock::now();
 
        // запускаем два потока
        thread th1(test1);
        thread th2(test2);
 
        // ждём завершения потоков
        th1.join();
        th2.join();
 
        // время окончанич
        auto finish = high_resolution_clock::now();
        auto dur = duration_cast<microseconds>(finish - start);
 
        // вывод времени исполнения в микро секундах
        cout << test << ") " << dur.count() << " us" << endl;
    }
 
    cout << "globalCount=" << globalCount << endl;
 
    return 0;
}
 
// если оставить SPINLOCK, то тестируется блокировка спинлоком
// если оставить STDMUTEX, то тестируется блокировка std-мьютексом
// если оставить WINMUTEX, то тестируется блокировка windows-мьютексом
// если убрать всё, то тестируется без блокировки
#define SPINLOCK
//#define STDMUTEX
//#define WINMUTEX
//#define WINCRITSECT
 
 
 
#ifdef SPINLOCK
 
#include <atomic>
atomic_flag locker = ATOMIC_FLAG_INIT;
 
void init() {}
 
void lock()
{
    while (locker.test_and_set(/*memory_order_acquire*/))
        this_thread::yield();
}
 
void unlock()
{
    locker.clear(/*memory_order_release*/);
}
 
#else
 
#ifdef STDMUTEX
 
#include <mutex>
mutex locker;
 
void init() {}
 
void lock()
{
    locker.lock();
}
 
void unlock()
{
    locker.unlock();
}
#else
 
#ifdef WINMUTEX
 
#include <Windows.h>
HANDLE locker = CreateMutex(0, FALSE, 0);
 
void init() {}
 
void lock()
{
    WaitForSingleObject(locker, INFINITE);
}
 
void unlock()
{
    ReleaseMutex(locker);
}
 
#else
 
#ifdef WINCRITSECT
 
#include <Windows.h>
 
CRITICAL_SECTION locker;
 
void init()
{
    InitializeCriticalSection(&locker);
}
 
void lock()
{
    EnterCriticalSection(&locker);
}
 
void unlock()
{
    LeaveCriticalSection(&locker);
}
 
#else
 
void init() {}
 
void lock()
{
}
 
void unlock()
{
}
 
#endif
 
#endif
 
#endif
 
#endif

Время работы в миллисекундах:
виндовый мьютексдофига
виндовая критическая секция60
без блокировок3
спинлок с мемори-флагами17
спинлок без мемори-флагов27
std-мьютекс58
спинлок с мемори-флагами, но без yield150
Короче, что-то я не так понимал этот yield, получился неожиданный для меня результат без него.
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
09.01.2020, 16:06
Цитата Сообщение от Ygg Посмотреть сообщение
Короче, что-то я не так понимал этот yield, получился неожиданный для меня результат без него.
Для меня тоже. Думаю, это из-за того, что эти потоки регулярно запускаются на одном процессоре
0
Эксперт С++
 Аватар для Avazart
8489 / 6156 / 615
Регистрация: 10.12.2010
Сообщений: 28,683
Записей в блоге: 30
09.01.2020, 16:33
Ygg, По Windows логичнее использовать критическую секцию, а не мютекс. Виндовс-мютекс для межпроцессорного взаимодействия.
std::mutex скорее всего построен на ней.
0
Любитель чаепитий
 Аватар для GbaLog-
3745 / 1801 / 566
Регистрация: 24.08.2014
Сообщений: 6,020
Записей в блоге: 1
10.01.2020, 05:52
Цитата Сообщение от hoggy Посмотреть сообщение
вот что значит "дороже"?
процессор зазря греть будет?
счет за электричество будет больше, или как?
дороже, в данном случае, это действительно "процессор зазря греть будет".
но, если это время незаметно в прикладных задачах,
то в высоконагруженных системах это будет иметь весомую цену,
т.к. там все ЦПУ/ядра должны утилизироваться максимально эффективно.
и, если код будет построен на спинлоках в тех местах,
в которых это не целесообразно, то это может замедлить программу в сравнении с мьютексом.
и наоборот.

Цитата Сообщение от hoggy Посмотреть сообщение
"медленные" мьютексы противопоставляются "быстрым" спинлокам.
цель бенчмарка - проиллюстрировать:
что с "быстрым" спинлоком программа начнет работать хотя бы чуточку,
но явно быстрее.
сам бенчмарк я запускал на очень мощной машине.
для ЦП нагрузка - камаринный укус.
бенчмарк показал:
спинлок с квадратными колесами лишь замедлил работу программы.
вот у тебя на спинлоках программа медленней заработала,
но это произошло, скорее всего, не потому, что ты плохие спинлоки сделал,
а потому, что конкретно в том месте программы/бенчмарка это было нецелесообразно.

то есть я хочу сказать, что твой бенчмарк лишь доказал то,
что конкретно в твоей программе не подходит спинлок,
а не то, что он в принципе плохой.
хотя, конечно, зависит ещё и от того, как ты его реализовал.

p.s. и я не по наслышке говорю это всё.
у меня была возможность ощутить прибавку производительности при замене мьютекса на спинлок,
но там был самый что ни на есть use case спинлока.
и сам спинлок, правда, был не самопальный.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
10.01.2020, 05:52

Как проинициализировать std::stack<const int> obj ( std::stack<int>{} );
добрый день. вопрос в коде: http://rextester.com/VCVVML6656 #include &lt;iostream&gt; #include &lt;stack&gt; //-std=c++14...

std::filesystem && std::asio и пр
Пытался найти хоть какие-то сроки включения всего этого в стандарт (так же ожидается lexical_cast, any, string_algo и т.д.) и вообщем везде...

Ошибка: E2034 Cannot convert 'int' to 'std::vector<std::vector<TRabbitCell,std::allocator<TRabbitCell>>...
Есть двухмерный вектор: std::vector&lt;std::vector&lt;TRabbitCell&gt; &gt; *cells(5, 10); Пытаюсь заполнить его объектами класса...

Перевод строк std::string, std::wstring в Unicode (String)
Собственно столкнулся с проблемой, как корректно перевести к примеру текст из Edit1-&gt;Text в std::string или std::wstring и...

This_thread::yield что делает?
Добрый день! Что делает функция yield(), можете привести небольшой пример, если не сложно?


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
31
Ответ Создать тему
Новые блоги и статьи
Беседа с ИИ о программистах, недопускающих к созданию и правке кода генеративные ИИ и причины этого
zorxor 21.09.2026
Раньше я радовался или получал некоторые эмоции, пусть небольшие, но всё же, от самого процесса написания кода, рекомпиляции и запуска, видя постепенное развитие программы и прочее. А теперь лень. . .
Мобильное приложение ColorStep
pavlinmavlin 17.09.2026
Реализовал приложение Красный, Зеленый, Синий в Unity3d + c#. Название изменил на ColorStep. Приложение прошло модерацию и теперь доступно для скачивания. Делал его сам, шаг за шагом — и вот,. . .
Запрет дублирования строк в табличной части
Maks 13.09.2026
Реализация из решения ниже выполнена на нетиповом справочнике "Нормы ТО" с табличной часть "Виды ТО", разработанного в КА2, со следующими реквизитами: - ВидТО (СправочникСсылка. ВидыТО); - ВидГСМ. . .
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru