2014-01-10 3 views
4

У меня возникли проблемы с пониманием некоторых концепций многопоточности. Я знаю основные принципы, но у меня возникают проблемы с пониманием того, когда отдельные потоки отправляются и используются ядрами.C++ - Вопросы о многопоточности

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

Вот мои вопросы:

  1. мне нужно запросить или даже учитывать количество ядер на машине или когда потоки работают, они автоматически отправляются в свободные сердечник ли?

  2. Может ли кто-нибудь показать мне пример цикла for, использующего потоки. Скажем, в каждой итерации цикла он будет вызывать функцию, используя другой поток. Я читал, что идеальное число потоков, которые должны быть активными, - это количество ядер. Как узнать, когда ядро ​​бесплатное или я должен проверить, присоединился ли он к основному потоку, и создать новый поток, когда он должен поддерживать определенное количество потоков.

Я что-то обманываю, или мои вопросы свидетельствуют о том, что я не понимаю понятия?

ответ

2
  1. Нет, вы можете использовать API, который держит что прозрачно, например POSIX потоки на Linux (pthread библиотеки).

  2. Этот ответ, вероятно, зависит от того, какой API вы используете, хотя многие API-интерфейсы разделяют основы потоков, такие как мьютексы. Здесь, однако, это пример pthreads (так как это единственный API-интерфейс потоковой передачи C/C++, который я знаю).

    #include <stdio.h> 
    #include <stdlib.h>   
    #include <pthread.h> 
    // Whatever other headers you need for your code. 
    
    #define MAX_NUM_THREADS 12 
    
    // Each thread will run this function. 
    void *worker(void *arg) 
    { 
        // Do stuff here and it will be 'in parallel'. 
    
        // Note: Threads can read from the same location concurrently 
        // without issue, but writing to any shared resource that has not been 
        // locked with, for example, a mutex, can cause pernicious bugs. 
    
        // Call this when you're done. 
        pthread_exit(NULL); 
    } 
    
    int main() 
    { 
        // Each is a handle for one thread, with 12 in total. 
        pthread_t myThreads[MAX_NUM_THREADS]; 
    
        // Create the worker threads. 
        for(unsigned long i = 0; i < numThreads; i++) 
        { 
         // NULL thread attributes struct. 
         // This initializes the threads with the default PTHREAD_CREATE_JOINABLE 
         // attribute; we know a thread is finished when it joins, see below. 
         pthread_create(&myThreads[i], NULL, worker, (void *)i); 
        } 
    
        void *status; 
        // Wait for the threads to finish. 
        for(unsigned int i = 0; i < numThreads; i++) 
        { 
         pthread_join(myThreads[i], &status); 
        } 
    
        // That's all, folks. 
        pthread_exit(NULL); 
    } 
    

Без слишком много деталей, что это довольно простой каркас для простого многопоточного приложения с помощью Pthreads.

Что касается ваших вопросов на лучший способ идти о применении этого к вашей программе:

Я предлагаю один поток для каждого файла, используя Threadpool Pattern, и вот почему:

один поток на файл намного проще, потому что нет совместного доступа, поэтому нет синхронизации. Вы можете изменить функцию worker на функцию decompressFile, передавая имя файла при каждом вызове pthread_create. Это в основном это. Здесь ваш шаблон потока пустяков.

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

Imagine Thread A имеет файл A open, а Thread B имеет файл B, но файл A и файл B находятся в совершенно разных секторах вашего диска.Поскольку алгоритм планирования вашей ОС переключается между Thread A и Thread B, ваш жесткий диск должен вращаться, как сумасшедший, чтобы идти в ногу, заставляя процессор (следовательно, ваша программа) ждать.

+0

Этот код выглядит довольно просто. Если бы у меня было большое количество файлов и я использовал один поток для каждого файла, было бы разумнее иметь столько потоков, сколько ядер?Как бы я использовал цикл, в котором говорят на четырехъядерном процессоре, обрабатывались 4 файла в 4 потоках, и когда один из них освобождался, был создан другой поток файлов? Или это не мудро или слишком сложно? –

+1

Чтобы упростить код, вы можете удалить все элементы 'threadAttributes'. 'PTHREAD_CREATE_JOINABLE' является значением по умолчанию, поэтому в приведенном выше коде вызов' pthread_create' с 'NULL' вместо' & threadAttributes' будет иметь тот же эффект. Также см. Http://linux.die.net/man/3/pthread_create и http://linux.die.net/man/3/pthread_attr_setdetachstate – sonicwave

+0

@sonicwave Приятный улов, редактирование. – Keeler

3

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

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

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

C++ 11 включает в себя thread library, который можно использовать для упрощения работы с потоками.

+0

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

+1

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

+0

@PladniusBrooks - вы можете использовать std :: thread_hardware_concurrency: http://en.cppreference.com/w/cpp/thread/thread/hardware_concurrency – Sean

0

Если вы находитесь в Windows, вы можете взглянуть на пулы потоков, хорошее описание можно найти здесь: http://msdn.microsoft.com/en-us/magazine/cc163327.aspx. Интересной особенностью этого объекта является то, что он обещает управлять потоками для вас. Он также выбирает оптимальное количество потоков в зависимости от спроса, а также от доступных ядер.

+0

Это потрясающе. К сожалению, мне нужно кросс-платформенное решение. +1 для ответа. –

1

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

Звучит все, что вам нужно, это выполнение параллельного цикла. В настоящее время существует множество библиотек C++, которые могут облегчить эту задачу для вас, например. TBB от Intel, PPL от Microsoft, AMD Bolt, Quay Quallcomm, чтобы назвать несколько. Вы можете сравнить условия лицензирования, поддерживаемые платформы, функциональность и сделать выбор, который наилучшим образом соответствует вашим потребностям.

Чтобы быть более конкретным и ответить на ваши вопросы:

1) Как правило, вы не должны иметь никакой необходимости знать/учитывать количество процессоров или ядер. Выберите библиотеку, которая абстрагирует эту деталь от вас и вашей программы. С другой стороны, если вы видите, что с настройками по умолчанию ЦП не полностью используется (например, из-за значительного числа операций ввода-вывода), может оказаться полезным запросить больше потоков, например. путем умножения значения по умолчанию на определенный коэффициент.

2) Эскиз для цикла производится параллельно с tbb::parallel_for и C++ 11 лямбда-функций:

#include <tbb/tbb.h> 
void ParallelFoo(std::vector<MyDataType>& v) { 
    tbb::parallel_for(size_t(0), v.size(), [&](int i){ 
     Foo(v[i]); 
    }); 
} 

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

Отказ от ответственности: Я являюсь разработчиком библиотеки Intel's TBB.

+0

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

+0

Я не совсем понимаю, о чем вы просили в комментарии. Если вопрос заключается в том, будет ли параллельная библиотека использовать потоки внизу, то я бы сказал, что для многоядерных процессоров, скорее всего, это будет (я могу представить реализации, не основанные на потоках, но это было бы довольно экзотично). –

Смежные вопросы