Подтвердить что ты не робот

Как "многопоточный" код

У меня есть число хрустящее приложение, написанное на C. Это своего рода основной цикл, который для каждого значения вызывает, для увеличения значений "i" - функцию, выполняющую некоторые вычисления. Я читал о многопоточности, и я подумываю о том, чтобы немного узнать об этом, в C. Интересно, может ли какой-то общий код, подобный моему, быть автоматически многопоточным и каким образом.

Спасибо

P.D. Чтобы получить представление о моем коде, скажем, что это примерно так:

main(...)
for(i=0;i<=ntimes;i++)get_result(x[i],y[i],result[i]);

...

void get_result(float x,float y,float result){
  result=sqrt(log (x) + log (y) + cos (exp (x + y));
(and some more similar mathematical operations)
}
4b9b3361

Ответ 1

Один из вариантов многопоточности вашего кода будет использовать pthreads (обеспечивает более точный контроль, чем OpenMP).

Предполагая, что x, y и result являются глобальными переменными массивами,

#include <pthread.h>

...

void *get_result(void *param)  // param is a dummy pointer
{
...
}

int main()
{
...
pthread_t *tid = malloc( ntimes * sizeof(pthread_t) );

for( i=0; i<ntimes; i++ ) 
    pthread_create( &tid[i], NULL, get_result, NULL );

... // do some tasks unrelated to result    

for( i=0; i<ntimes; i++ ) 
    pthread_join( tid[i], NULL );
...
}

(Скомпилируйте свой код с помощью gcc prog.c -lpthread)

Ответ 2

Если задача сильно параллелизуема и ваш компилятор является современным, вы можете попробовать OpenMP. http://en.wikipedia.org/wiki/OpenMP

Ответ 3

Вы должны посмотреть на openMP для этого. Пример C/С++ на этой странице похож на ваш код: https://computing.llnl.gov/tutorials/openMP/#SECTIONS

#include <omp.h>
#define N     1000

main ()
{

int i;
float a[N], b[N], c[N], d[N];

/* Some initializations */
for (i=0; i < N; i++) {
  a[i] = i * 1.5;
  b[i] = i + 22.35;
  }

#pragma omp parallel shared(a,b,c,d) private(i)
  {

  #pragma omp sections nowait
    {

    #pragma omp section
    for (i=0; i < N; i++)
      c[i] = a[i] + b[i];

    #pragma omp section
    for (i=0; i < N; i++)
      d[i] = a[i] * b[i];

    }  /* end of sections */

  }  /* end of parallel section */

}

Если вы предпочитаете не использовать openMP, вы можете использовать либо pthreads, либо clone/wait напрямую.

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

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

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

Ответ 4

Если вы надеетесь предоставить concurrency для одного цикла для каких-то научных вычислений или аналогичных, OpenMP, как @Novikov говорит, на самом деле это ваш лучший выбор; это то, для чего он был предназначен.

Если вы хотите изучить более классический подход, который более типично вы увидите в приложении, написанном на C... На POSIX вы хотите pthread_create() et al. Я не уверен, что ваш фон может быть с concurrency на других языках, но прежде чем заходить слишком глубоко в это, вы будете достаточно хорошо знать свои примитивы синхронизации (мьютексы, семафоры и т.д.), А также понять, когда вам нужно будет их использовать. Эта тема может быть цельной книгой или набором SO-вопросов для себя.

Ответ 5

В зависимости от ОС вы можете использовать потоки posix. Вместо этого вы могли бы реализовать многопоточность без стека с использованием государственных машин. Существует действительно хорошая книга под названием "встроенная многозадачность" Кейта Э. Кертиса. Это просто аккуратно созданный набор операторов case switch. Отлично работает, я использовал его на всем, от Apple Mac, кроличьего полупроводника, AVR, ПК.

Вели

Ответ 6

хорошим упражнением для обучения параллельному программированию на любом языке будет работа над реализацией пула потоков.
В этом шаблоне вы создаете некоторые потоки заранее. Эти потоки рассматриваются как ресурс. Объект/структура пула потоков используется для назначения заданной пользователем задачи этим потокам для выполнения. Когда задача будет завершена, вы сможете собрать ее результаты. Вы можете использовать пул потоков как шаблон общего назначения для concurrency. Основная идея может быть похожа на

#define number_of_threads_to_be_created 42
// create some user defined tasks
Tasks_list_t* task_list_elem = CreateTasks();
// Create the thread pool with 42 tasks
Thpool_handle_t* pool = Create_pool(number_of_threads_to_be_created);

// populate the thread pool with tasks
for ( ; task_list_elem; task_list_elem = task_list_elem->next) {
   add_a_task_to_thpool (task_list_elem, pool);
}
// kick start the thread pool
thpool_run (pool);

// Now decide on the mechanism for collecting the results from tasks list.
// Some of the candidates are:
// 1. sleep till all is done (naive)
// 2. pool the tasks in the list for some state variable describing that the task has
//    finished. This can work quite well in some situations
// 3. Implement signal/callback mechanism that a task can use to signal that it has 
//    finished executing.

Механизм сбора данных из задач и количества потоков, используемых в пуле, должен быть выбран с учетом ваших требований и возможностей аппаратного обеспечения и среды выполнения.
Также обратите внимание, что этот шаблон ничего не говорит о том, как вы должны "синхронизировать" свои задачи друг с другом/вне среды. Также обработка ошибок может быть немного сложной (пример: что делать, когда одна задача выходит из строя). Эти два аспекта должны быть задуманы заранее - они могут ограничить использование шаблона пула потоков.

О пуле потоков:
http://en.wikipedia.org/wiki/Thread_pool_pattern
http://docs.oracle.com/cd/E19253-01/816-5137/ggedn/index.html

Хорошая литература о pthreads, чтобы идти:
http://www.advancedlinuxprogramming.com/alp-folder/alp-ch04-threads.pdf

Ответ 7

Компилятор Intel С++ на самом деле способен автоматически парализовать ваш код. Это просто компилятор, который нужно включить. Он работает не так хорошо, как OpenMP (т.е. Он не всегда преуспевает или результирующая программа работает медленнее). С сайта Intel: "Автоматическая распараллеливание, которое запускается опцией -parallel (Linux * OS и Mac OS * X) или /Qparallel (Windows * OS), автоматически идентифицирует те структуры цикла, которые содержат parallelism. Во время компиляции компилятор автоматически пытается для деконструирования кодовых последовательностей в отдельные потоки для параллельной обработки. Другим усилиям программиста не требуется."

Ответ 8

Чтобы конкретно задать " автоматически многопоточную" часть вопроса OP:

Один действительно интересный взгляд на программирование parallelism был разработан на языке под названием Cilk Plus, который был изобретен MIT и теперь принадлежит Intel. Чтобы процитировать Википедию, идея состоит в том, что

"программист должен нести ответственность для экспонирования parallelism, определение элементов, которые могут безопасно выполняться параллельно; должно затем оставить на время выполнения окружающей среды, в частности планировщик, чтобы решить во время выполнения как разделить работу между процессорами".

Cilk Plus - это надмножество стандартного С++. Он содержит несколько дополнительных ключевых слов (_Cilk_spawn, _Cilk_sync и _Cilk_for), которые позволяют программисту тегировать часть своей программы как параллелизуемую. Программист не требует, чтобы какой-либо код выполнялся в новом потоке, они просто позволяют лёгкому планировщику времени запуска порождать новый поток, если и только если это действительно то, что нужно делать в определенных условиях выполнения.

Чтобы использовать Cilk Plus, просто добавьте его ключевые слова в свой код и создайте с помощью компилятор Intel С++.

Ответ 9

Ваш код не будет автоматически многопоточен компилятором, если это ваш вопрос. Обратите внимание, что сами стандарты C ничего не знают о многопоточности, поскольку можно ли использовать многопоточность или нет, не зависит от языка, который вы используете для кодирования, но на целевой платформе, для которой вы кодируете. Код, написанный на C, может работать практически во всем, что существует для компилятора C. Компилятор C даже существует для компьютера C64 (почти полностью соответствует ISO-99); однако для поддержки нескольких потоков платформа должна иметь операционную систему, поддерживающую это, и обычно это означает, что должна присутствовать, по крайней мере, определенная функциональность ЦП. Операционная система может выполнять многопоточность почти исключительно в программном обеспечении, это будет очень медленным, и защита памяти не будет, но это возможно, однако даже в этом случае вам понадобятся по крайней мере программируемые прерывания.

Итак, как писать многопоточный код C полностью зависит от операционной системы вашей целевой платформы. Существуют системы соответствия POSIX (OS X, FreeBSD, Linux и т.д.) И системы, для которых есть их библиотека (Windows). Некоторые системы имеют для нее больше, чем библиотеку (например, в ОС X есть библиотека POSIX, но есть также менеджер углеродных потоков, который вы можете использовать в C (хотя, я думаю, сейчас это довольно унаследовано).

Конечно, существуют кросс-платформенные библиотеки потоков, и некоторые современные компиляторы поддерживают такие вещи, как OpenMP, где компилятор автоматически создает код для создания потоков на выбранной вами целевой платформе; но не многие компиляторы поддерживают его, а те, которые его поддерживают, обычно не являются полнофункциональными. Обычно вы получаете самую широкую поддержку системы, используя потоки POSIX, чаще называемые "pthreads". Единственной основной платформой, не поддерживающей ее, является Windows, и здесь вы можете использовать бесплатные сторонние библиотеки, такие как этот. Также существует несколько других портов (Cygwin имеет одно подтверждение). Если у вас будет один пользовательский интерфейс в один прекрасный день, вы можете использовать кросс-платформенную библиотеку, например wxWidgets или SDL, предлагая согласованную многопоточную поддержку на всех поддерживаемых платформах.

Ответ 10

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

Скажем, у вас есть 2 ядра, а ntimes - 100, затем 100/2 = 50, поэтому создайте 2 версии программы, где первые итерации от 0 до 49, а остальные от 50 до 99. Запустите их оба, ваш ядра должны быть достаточно заняты.

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

Ответ 11

Вы можете использовать pthreads для выполнения многопоточности в C. вот простой пример, основанный на pthreads.

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

void *mythread1();  //thread prototype
void *mythread2();

int main(){
    pthread_t thread[2];
    //starting the thread
    pthread_create(&thread[0],NULL,mythread1,NULL);
    pthread_create(&thread[1],NULL,mythread2,NULL);
    //waiting for completion
    pthread_join(thread[0],NULL);
    pthread_join(thread[1],NULL);


    return 0;
}

//thread definition
void *mythread1(){
    int i;
    for(i=0;i<5;i++)
        printf("Thread 1 Running\n");
}
void *mythread2(){
    int i;
    for(i=0;i<5;i++)
        printf("Thread 2 Running\n");
}

Ссылка: Программа C для реализации многопоточности-многопоточности в C

Ответ 12

C11 темы в Glibc 2,28.

Протестировано в Ubuntu 18.04 (glibc 2.27) путем компиляции glibc из исходного кода: Несколько библиотек glibc на одном хосте

Пример из: https://en.cppreference.com/w/c/language/atomic

#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>

atomic_int acnt;
int cnt;

int f(void* thr_data)
{
    for(int n = 0; n < 1000; ++n) {
        ++cnt;
        ++acnt;
        // for this example, relaxed memory order is sufficient, e.g.
        // atomic_fetch_add_explicit(&acnt, 1, memory_order_relaxed);
    }
    return 0;
}

int main(void)
{
    thrd_t thr[10];
    for(int n = 0; n < 10; ++n)
        thrd_create(&thr[n], f, NULL);
    for(int n = 0; n < 10; ++n)
        thrd_join(thr[n], NULL);

    printf("The atomic counter is %u\n", acnt);
    printf("The non-atomic counter is %u\n", cnt);
}

GitHub upstream.

Скомпилируйте и запустите:

gcc -std=c11 main.c -pthread
./a.out

Возможный вывод:

The atomic counter is 10000
The non-atomic counter is 8644

Скорее всего, неатомарный счетчик будет меньше атомарного из-за быстрого доступа через потоки к неатомарной переменной.

TODO: разберите и посмотрите, что ++acnt; компилирует.

POSIX темы

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <stdlib.h>
#include <pthread.h>

enum CONSTANTS {
    NUM_THREADS = 1000,
    NUM_ITERS = 1000
};

int global = 0;
int fail = 0;
pthread_mutex_t main_thread_mutex = PTHREAD_MUTEX_INITIALIZER;

void* main_thread(void *arg) {
    int i;
    for (i = 0; i < NUM_ITERS; ++i) {
        if (!fail)
            pthread_mutex_lock(&main_thread_mutex);
        global++;
        if (!fail)
            pthread_mutex_unlock(&main_thread_mutex);
    }
    return NULL;
}

int main(int argc, char **argv) {
    pthread_t threads[NUM_THREADS];
    int i;
    fail = argc > 1;
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_create(&threads[i], NULL, main_thread, NULL);
    for (i = 0; i < NUM_THREADS; ++i)
        pthread_join(threads[i], NULL);
    assert(global == NUM_THREADS * NUM_ITERS);
    return EXIT_SUCCESS;
}

Скомпилируйте и запустите:

gcc -std=c99 pthread_mutex.c -pthread
./a.out
./a.out 1

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

Проверено на Ubuntu 18.04. GitHub upstream.