Многопоточное программирование — это специализированная форма параллельного программирования, которая предполагает выполнение нескольких потоков в рамках одного процесса или приложения. Объясню - каждая система состоит из процессов, а процесс состоит из потоков. Потоков может быть как несколько так и один. То есть один процесс выполняет несколько действий одновременно. Многопоток нужен в основном для оптимизации использования ресурсов, для программирования интерфейсов - как упомянул раннее выполнение нескольких действийй одновременно. Многопоточность незаменима тогда, когда необходимо, чтобы графический интерфейс продолжал отзываться на действия пользователя во время выполнения некоторой обработки информации. Не будем тянуть, а перейдем к делу.

#include <gtk-4.0/gtk/gtk.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static void *thread_function(void *data) {
    for (int i = 1; i <= 10; i++) {
        printf("Thread: %d\n", i);
        sleep(1); 
    }
    return NULL;
}

static void on_button_clicked(GtkWidget *widget, gpointer data) {
    pthread_t thread;
    if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
        g_print("Ошибка создания потока\n");
    }
    pthread_detach(thread);
}

int main(int argc, char *argv[]) {
    GtkWidget *window;
    GtkWidget *button;

    gtk_init(&argc, &argv);

    window = gtk_window_new();
    gtk_window_set_title(GTK_WINDOW(window), "Многопоточная программа");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    button = gtk_button_new_with_label("Запустить поток");
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), NULL);
    gtk_window_set_child(GTK_WINDOW(window), button);

    gtk_widget_show(window);
    gtk_main();

    return 0;
}

//gcc threading.c -o thread_gui.o -lpthread Надо было добавить чтобы выводило и номер потока но что-то до меня не много не дошлоНадо было добавить чтобы выводило и номер потока но что-то до меня не много не дошло

Тут я написал небольшое приложение которое создает поток и выводит с его помощью числа от одного до 10 с небольшой задержкой. Выглядит это как то так:

Надо было добавить чтобы выводило и номер потока но что-то до меня не много не дошло

По нажатии кнопки создается поток который просто выводит числа от 1 до 10. То есть если нажать два раза одновременно то вывод будет таким

Thread: 1
Thread: 1
Thread: 2
Thread: 2
Thread: 3
Thread: 3
Thread: 4
Thread: 4
Thread: 5
Thread: 5

И так дальше до 10.

Разбор функций

Основные функции pthread

  • pthread_create(); — создание потока.

  • pthread_join(); — ожидание завершения потока.

  • pthread_exit(); — завершение текущего потока.

  • pthread_detach(); — отсоединение потока

  • pthread_mutex_lock();, pthread_mutex_unlock(); — блокировка/разблокировка мьютекса.

  • pthread_cond_wait();, pthread_cond_signal(); — работа с условными переменными.

Для многопоточности в C и C++ предназначена библиотека posix-thread.

Каждый поток должен иметь функцию принимающую и возвращающую void* (void - пустое или неопределенное значение), в данном случае это *thread_function. Также обязательно должен иметь переменную типа pthread_t.

Так вот, чтобы создать поток нужно использовать

pthread_create(&thread, NULL, thread_function, &thread_num);

  • &thread - указатель на pthread_t переменную

  • NULL - аргумент для функции и атрибуты потока (обычно их нет).

  • thread_function - функция потока

  • &thread_num - указатель на переменную с номером потока (не обязательно).

//Прототип функции
int pthread_create( pthread_t *thread,
                    const pthread_attr_t *attr,
                    void * (*start_routine)( void * ),
                    void *arg );

Пример использования в 16 строке кода в начале.

Так, вроде понятно. Попробуем использовать

#include <pthread.h>
#include <stdio.h>
 
void* some_work(void* arg) 
{
    for(int i = 0; i < 5; ++i) 
    {
        puts(arg);
    }
    return NULL;
}
int main(void) 
{ 
    pthread_t thread1, thread2;

    pthread_create(&thread1, NULL, some_work, "Hello World!"); 
    pthread_create(&thread2, NULL, some_work, "Hello from second thread!"); 
//18 строка
    printf("End\n");
    return 0;
}
/*
Вывод:
End...
Hello World
Hello World
Hello World
Hello World
Hello World

Авторы примера: https://metanit.com/c/tutorial/11.1.php
*/

В любом случае мы сталкиваемся с проблемой - потоки не отрабатывают полностью, а программа завершается. Дело в том, что оператор return в функции main приводит к завершению программы и соответственно процесса программы и всех ее потоков. Если поток не успел отработать до завершения процесса, то он тоже завершается. - metanit

Как же это починить?

Если мы добавим pthread_exit(NULL); в 18 строку то все будет правильно за исключением того что пропадет "End" по тому что все потоки будут закрыты. Как же это починить?

В некоторых случаях нужно подождать пока выполниться поток, разумеется для этого предусмотрена функция

pthread_join(thread, NULL);

thread - Переменная потока (pthread_t).

NULL - Или указатель на переменную, где функция может хранить значение, которое возвращает pthread_exit .

//Прототип функции
int pthread_join( pthread_t thread,
                  void **value_ptr );

Пример использования

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* increment(void* arg) {
    int thread = *(int*)arg; //Thread num
    for (int i = 0; i < 100000; ++i) //Отсчет до 100000 
    {
        pthread_mutex_lock(&mutex);
        counter++;
        printf("Thread %d: counter = %d\n", thread, counter); 
        pthread_mutex_unlock(&mutex); //Mutex дальше в статье
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    int thread1_id = 1, thread2_id = 2;

    //Создаем потоки 
    pthread_create(&t1, NULL, increment, &thread1_id);
    sleep(1);
    pthread_create(&t2, NULL, increment, &thread2_id);
  
    //Ожидаем их выполнения
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

Если нам не требуется ожидать выполнения потока то мы можем просто его отсоединить и очистить ресурсы через

pthread_detach(thread);

thread - Переменная потока (pthread_t).

Те самые аргументы в функциях взаимодействия с потоками вместо которых я писал NULL

В функции pthread_create второй аргумент (в моем коде - NULL) отвечает за атрибуты потока. Если вместо NULL передать указатель на предварительно настроенную структуру pthread_attr_t, можно управлять характеристиками создаваемого потока.

pthread_attr_t позволяет задать:

  1. Размер стека потока (stack size).

  2. Состояние отсоединения (detached state):

    • Поток может быть создан как отсоединенный (не требует pthread_join).

  3. Политику планирования (scheduling policy):

    • Например, SCHED_FIFO, SCHED_RR, SCHED_OTHER.

  4. Приоритет потока (scheduling priority).

  5. Наследование атрибутов планирования (inheritsched):

    • Может ли поток наследовать параметры планирования от родительского потока.

  6. Область стека (stack address).

    #include <pthread.h>
    #include <stdio.h>
    
    void* thread_func(void* arg) {
        printf("Поток с настраиваемыми атрибутами\n");
        return NULL;
    }
    
    int main() {
        pthread_t thread;
        pthread_attr_t attr;
    
        // Инициализация атрибутов
        pthread_attr_init(&attr);
    
        // Установка отсоединенного состояния
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
        // Установка размера стека (например, 2 МБ)
        size_t stack_size = 2 * 1024 * 1024;
        pthread_attr_setstacksize(&attr, stack_size);
    
        // Создание потока с атрибутами
        int result = pthread_create(&thread, &attr, thread_func, NULL);
        if (result != 0) {
            perror("Ошибка создания потока");
            return 1;
        }
    
        // Уничтожение атрибутов 
        pthread_attr_destroy(&attr);
    
        // Для отсоединенного потока pthread_join не требуется
        sleep(1); 
        return 0;
    }

    Инициализация и использование pthread_attr_t

    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
    1. Перед использованием структуры pthread_attr_t ее нужно проинициализировать с помощью функции

      int pthread_attr_init(pthread_attr_t *attr);
      • Эта функция устанавливает все атрибуты в значения по умолчанию.

      • После завершения работы со структурой необходимо освободить ресурсы

Размер стека

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

Пример

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 1024 * 1024); // Установить размер стека в 1 МБ

Состояние отсоединения

Поток может быть создан как "отсоединенный" (detached). Это означает, что его ресурсы будут автоматически освобождены после завершения работы, без необходимости вызывать pthread_join.

Функция установки состояния

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

Значения detachstate :

  • PTHREAD_CREATE_JOINABLE: Поток требует вызова pthread_join (по умолчанию).

  • PTHREAD_CREATE_DETACHED: Поток будет автоматически освобожден после завершения

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

Политика планирования (scheduling policy)

Политика планирования потоков (thread scheduling policy) в POSIX-потоках (pthread) определяет, каким образом операционная система распределяет процессорное время между потоками исполнения. Политика планирования влияет на то, как потоки конкурируют за ресурсы и как они выполняются на различных уровнях приоритета.

Можно указать политику планирования потока:

int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

Доступные политики:

  • SCHED_FIFO: Первый пришел — первый обслужен (FIFO).

  • SCHED_RR: Ротационное планирование (Round Robin).

  • SCHED_OTHER: Обычная политика планирования (по умолчанию).

pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

Приоритет потока (scheduling priority)

Приоритет потока можно установить с помощью

struct sched_param {
    int sched_priority; // Значение приоритета
};

int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);

Приоритет должен находиться в диапазоне, зависящем от политики планирования:

  • Для SCHED_FIFO и SCHED_RR: от sched_get_priority_min(SCHED_FIFO) до sched_get_priority_max(SCHED_FIFO).

  • Для SCHED_OTHER: обычно используется динамическое планирование, и приоритет игнорируется.

Пример:

struct sched_param param;
param.sched_priority = 10; // Установить приоритет
pthread_attr_setschedparam(&attr, &param);

Адрес стека (stack address)

Можно указать пользовательский адрес для стека поток

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);

Это полезно, если вы хотите использовать специфический участок памяти для стека.

Пример:

void *custom_stack = malloc(1024 * 1024); // Выделить 1 МБ памяти
pthread_attr_setstack(&attr, custom_stack, 1024 * 1024);

Мьютексы и гонки данных

Мьютекс - это некий механизм который изолирует shared данные когда их использует один поток дабы избежать гонок данных. (Гонки данных возникают, когда к общему ресурсу непредсказуемо обращаются несколько задач.)

Применяется он так, но на всякий вот определение с википедии

Мью́текс (англ. mutex, от mutual exclusion — «взаимное исключение») — примитив синхронизации, обеспечивающий взаимное исключение исполнения критических участков кода[1]. Классический мьютекс отличается от двоичного семафора наличием эксклюзивного владельца, который и должен его освобождать (то есть переводить в незаблокированное состояние)[2]. От спинлока мьютекс отличается передачей управления планировщику для переключения потоков при невозможности захвата мьютекса[3]. Встречаются также блокировки чтения-записи, именуемые разделяемыми мьютексами и предоставляющие помимо эксклюзивной блокировки общую, позволяющую совместно владеть мьютексом, если нет эксклюзивного владельца[4].

Как им пользоваться?

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

Для начала - инициализация

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • *mutex - указатель на pthread_mutex_t переменную

  • *attr - атрибуты мьютекса (обычно NULL для значений по умолчанию).

Захват мьютекса

int pthread_mutex_lock(pthread_mutex_t *mutex);

Блокирует доступ к защищенному ресурсу. Если мьютекс уже занят другим потоком, текущий поток блокируется до освобождения мьютекса.

Попытка захвата мьютекса

int pthread_mutex_trylock(pthread_mutex_t *mutex);

Пытается захватить мьютекс. Если мьютекс занят, возвращает ошибку (EBUSY) вместо блокировки потока.

Если есть mutex_lock то есть и mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Логично что это разблокирует мьютекс, а последняя функция: уничтожение мьютекс

int pthread_mutex_destroy(pthread_mutex_t *mutex);

На этом с мьютексом все. Он не сложный. Закончим примером использования

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int shared_variable = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* increment(void* arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_mutex_lock(&mutex); // Защита доступа к shared_variable
        shared_variable++;
        pthread_mutex_unlock(&mutex); // Освобождение мьютекса
    }
    return NULL;
}

int main() {
    pthread_t thread1, thread2;

    // Создание потоков
    pthread_create(&thread1, NULL, increment, NULL);
    pthread_create(&thread2, NULL, increment, NULL);

    // Ожидание завершения потоков
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // Вывод результата
    printf("Shared variable: %d\n", shared_variable);

    // Уничтожение мьютекса
    pthread_mutex_destroy(&mutex);

    return 0;
}

Условные переменные

Условные переменные pthread_cond_t - это механизм синхронизации потоков, позволяющий одному или нескольким потокам ожидать выполнения определенного условия, пока другой поток не уведомит об его изменении. Они используются вместе с мьютексами для безопасного ожидания и уведомления о состоянии shared (общих) данных. Само по себе условие является просто переменной с содержанием PTHREAD_COND_INITIALIZER

Инициализация условной переменной

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • cond: Указатель на условную переменную.

  • attr: Атрибуты условной переменной (обычно NULL для значений по умолчанию).

Сразу о том как ее уничтожить

int pthread_cond_destroy(pthread_cond_t *cond); 

Ожидание условия

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • Поток блокируется до тех пор, пока другой поток не вызовет сигнал (pthread_cond_signal или pthread_cond_broadcast).

  • Мьютекс должен быть захвачен перед вызовом pthread_cond_wait, и он автоматически освобождается при входе в функцию и снова захватывается при выходе.

Также существует ожидание условие с таймаутом

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

Работает аналогично pthread_cond_wait, но добавляет возможность установить таймаут ожидания.

Оптимизация с помощью многопоточности

Оптимизация кода с помощью многопоточности — это процесс разделения задачи на несколько независимых подзадач, которые могут выполняться параллельно в разных потоках, что позволяет эффективнее использовать ресурсы процессора и ускорить выполнение программы. Однако важно помнить, что многопоточность не всегда приводит к улучшению производительности, если она используется неправильно. Это очень важно уметь, одна из очень крутых фишек многопотока.

Основные подходы к оптимизации с помощью многопоточности

1. Разделение вычислений

  • Разбейте задачу на несколько независимых частей, которые можно выполнять параллельно.

  • Пример: при работе с массивами или матрицами каждый поток может обрабатывать свою часть данных (например, строки матрицы).

    #include <pthread.h>
    #include <stdio.h>
    
    #define SIZE 1000000
    
    int array[SIZE];
    long sum = 0;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    void* calculate_sum(void* arg) {
        int start = *((int*)arg);
        int end = start + SIZE / 4; // Каждый поток обрабатывает четверть массива
        long local_sum = 0;
    
        for (int i = start; i < end; i++) {
            local_sum += array[i];
        }
    
        pthread_mutex_lock(&mutex); // Защита доступа к общему ресурсу
        sum += local_sum;
        pthread_mutex_unlock(&mutex);
    
        return NULL;
    }
    
    int main() {
        pthread_t threads[4];
        int starts[] = {0, SIZE / 4, SIZE / 2, 3 * SIZE / 4};
    
        for (int i = 0; i < 4; i++) {
            pthread_create(&threads[i], NULL, calculate_sum, &starts[i]);
        }
    
        for (int i = 0; i < 4; i++) {
            pthread_join(threads[i], NULL);
        }
    
        printf("Sum: %ld\n", sum);
        pthread_mutex_destroy(&mutex);
        return 0;
    }

    Тут вообще сложно привести пример, но я попытался, для меня вообще все примеры одинакого сложно придумывать, а потом их говнокодить.

2. Использование пула потоков

  • Вместо создания нового потока для каждой задачи используйте пул потоков, где несколько потоков ожидают задач из очереди.

  • Это снижает накладные расходы на создание и уничтожение потоков.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define THREAD_COUNT 4

typedef struct {
    int id;
} Task;

Task* tasks;
int task_count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int finished = 0;

void* worker(void* arg) {
    while (1) {
        Task* task = NULL;

        pthread_mutex_lock(&mutex);
        while (task_count == 0 && !finished) {
            pthread_cond_wait(&cond, &mutex);
        }

        if (task_count > 0) {
            task = &tasks[0];
            for (int i = 0; i < task_count - 1; i++) {
                tasks[i] = tasks[i + 1];
            }
            task_count--;
        }

        if (finished && task_count == 0) {
            pthread_mutex_unlock(&mutex);
            break;
        }

        pthread_mutex_unlock(&mutex);

        if (task != NULL) {
            printf("Thread %ld processing task %d\n", (long)arg, task->id);
        }
    }
    return NULL;
}

int main() {
    pthread_t threads[THREAD_COUNT];

    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_create(&threads[i], NULL, worker, (void*)(long)i);
    }

    for (int i = 0; i < 10; i++) {
        Task t = {i};
        pthread_mutex_lock(&mutex);
        tasks = realloc(tasks, (task_count + 1) * sizeof(Task));
        tasks[task_count++] = t;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }

    pthread_mutex_lock(&mutex);
    finished = 1;
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);

    for (int i = 0; i < THREAD_COUNT; i++) {
        pthread_join(threads[i], NULL);
    }

    free(tasks);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

3. Асинхронная обработка

  • Используйте асинхронные операции для выполнения длительных задач (например, чтение/запись файлов, сетевые операции) без блокировки основного потока.

  • В C можно использовать функции pthread вместе с библиотеками для асинхронной работы.

4. Балансировка нагрузки

  • Распределите задачи равномерно между потоками, чтобы избежать ситуации, когда один поток завершает работу раньше других.

  • Это можно реализовать динамически, например, с использованием очередей задач.

Асинхроннось и многопоточность, как они связаны и почему это НЕ одно и тоже

Для начала сравним определения, на первый взгляд они очень похожи.

Асинхронность - код может продолжать выполняться, не ожидая завершения долгих операций — загрузки данных с сервера или чтения файла с диска.

Многопоточность — способность платформы (например виртуальной машины, операционной системы и т. д.) или приложения выполнять одновременно, то есть без предписанного порядка во времени, несколько параллельных задач — потоков.

Вчитаемся....

  1. Многопоточность :

    • Это создание нескольких потоков выполнения внутри одного процесса.

    • Каждый поток работает параллельно (или псевдопараллельно) на разных ядрах процессора.

    • Требует синхронизации доступа к общим ресурсам (мьютексы, условные переменные).

    • Хорошо подходит для CPU-ограниченных задач.

  2. Асинхронность :

    • Это модель программирования без блокировки выполнения при ожидании операций (например, ввода-вывода).

    • Использует один поток, но позволяет "переключаться" между задачами при ожидании событий.

    • Основана на callback-функциях, промисах или async/await.

    • Подходит для I/O-ограниченных задач (сетевые запросы, работа с файлами).

Многопоточность

Многопоточность - это один из способов реализации конкурентного исполнения путем выделения абстракции "рабочего потока" (worker thread).

Потоки "абстрагируют" от пользователя низкоуровневые детали и позволяют выполнять более чем одну работу "параллельно". Операционная система, среда исполнения или библиотека прячет подробности того, будет многопоточное исполнение конкурентным (когда потоков больше чем физических процессоров), или параллельным (когда число потоков меньше или равно числу процессоров и несколько задач физически выполняются одновременно).

Асинхронное исполнение

Асинхронность (asynchrony) подразумевает, что операция может быть выполнена кем-то на стороне: удаленным веб-узлом, сервером или другим устройством за пределами текущего вычислительного устройства.

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

Итого

  • Многопоточность = несколько потоков, выполняющихся параллельно.

  • Асинхронность = один поток, выполняющий задачи неблокирующими способами.

Заключение

Вообще это одна из первых моих статей, надеюсь получилась полезной. Хотя на мой взгляд получилось слишком много кода и слишком мало текста. Ну уже лучше чем в телеграме по тому что там вообще нет таких возможностей для написания постов. В общем спасибо за внимание ,рассчитываю на конструктивную критику.

Заключение о многопоточности

Многопоточность - это вполне сложный, важный и мощный инструмент который вряд ли сможешь выучить за одну статью на Хабре, тем-более если эту статью написал новичок в гайдах и документациях. Она переполнена примерами кода и описанием функций и теории многопоточности, что явно должно вам помочь. Удачи!

Комментарии (8)


  1. slavamuravey
    11.02.2025 13:48

    Здравствуйте, спасибо за статью! Вы пишете:

    Многопоточность незаменима тогда, когда необходимо, чтобы графический интерфейс продолжал отзываться на действия пользователя во время выполнения некоторой обработки информации.

    Скорее, не незаменима, а является одним из способов обеспечения отзывчивости интерфейса. Например, главный поток в браузере может успеть и страницу отрендерить, и сделать еще какие-то вычисления, главное вовремя возвращаться к рендерингу страницы.


    1. Namilsky Автор
      11.02.2025 13:48

      Спасибо, в следующей статье формулировки будут точнее


    1. Jijiki
      11.02.2025 13:48

      у автора всё нормально изложено, технически пример на гтк - связь X11/SDL/glfw - все эти библиотеки кто прокидывает калбеки, кто прокидывает концы опроса с thread_pool, а на Иксах без калбека или трид пула на Input придётся делать как раз одно из двух что предоставляет даже гтк(повторы, задержка, поэтому придётся делать своё -x11 ), на виндовсе асинхронная обработка уже преврщенная в функцию, а так да чтоб и то и то работало


  1. unreal_undead2
    11.02.2025 13:48

    А почему это в хабе разработки под винду? Ожидал что-то кроссплатформенное, а тут чистый pthread - тоже полезно в образовательных целях, но не так интересно. И если нужно именно распараллелить работу используя тредпул - в практических целях лучше посмотреть на TBB или OpenMP, хотя знать, как под капотом работают потоки, конечно стоит.

    Хорошо, что чётко расписали отличие многопоточности и асинхронности - часто можно обойтись и без тредов.


  1. Mcublog
    11.02.2025 13:48

    Спасибо за статью!

    Ещё было бы интересно почитать про тренды в контексте си это про добавленный заголовок в си11 -- threads.h

    Понятно, что там практически тоже самое, как в posix тредах. но инфы про его использование практически нет. На сколько он поддержан компиляторами и кроссплатформенный

    У самого руки не доходят пощупать, так что буду рад если найдете время и самое главное желание в этом разбраться)


  1. rukhi7
    11.02.2025 13:48

    Тут вообще сложно привести пример, но я попытался, для меня вообще все примеры одинакого сложно придумывать, а потом их говнокодить.

    Прям как в анекдоте про орла с экипажем. Зачем же так напрягаться то?


  1. rukhi7
    11.02.2025 13:48

    По нажатии кнопки создается поток который просто выводит числа от 1 до 10. То есть если нажать два раза одновременно то

    Нажать два раза одновременно звучит прикольно!

    Мьютекс - это некий механизм который изолирует shared данные когда их использует один поток дабы избежать гонок данных.

    Точно? Именно когда их использует только один поток?

    Вместо создания нового потока для каждой задачи используйте пул потоков

    Я так понимаю вы рекомендуете каждый раз писать свою(вашу) самодельную реализацию пула потоков?

    В C можно использовать функции pthread вместе с библиотеками для асинхронной работы

    мне казалось что библиотека с pthread-ами и есть библиотека для асинхронной работы в Си.

    ...

    Очень прикольная статья, как тут плюс не поставить!


    1. Namilsky Автор
      11.02.2025 13:48

      Спасибо что указали на ошибки, отредактирую в ближайшее время