#c #multithreading #pthreads
#c #многопоточность #pthreads
Вопрос:
Итак, у меня есть алгоритм быстрой сортировки, который я хочу запустить в двух разных потоках, идея состоит в том, чтобы одновременно сортировать 2 независимые половины массива (это не должно быть проблемой из-за быстрой сортировки раздела).
Мой код выглядит следующим образом:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void troca (int *v, int i, int j);
int partition (int *v, int ini, int fim);
void quicksort (int *v, int ini, int fim, int size);
typedef struct {
int ini, mid, fim;
} thread_arg;
int size;
int *v;
void *thread_func(void* arg){
thread_arg *a = arg;
quicksort(v, a->ini, a->mid, a->fim);
printf("done inn");
return NULL;
}
int main()
{
// initializations
scanf("%d", amp;size);
v = malloc(size * sizeof(int));
// read array
for (int i = 0; i < size; i)
scanf("%d", amp;v[i]);
// arguments
thread_arg argument1, argument2;
int mid = partition(v, 0, size-1);
argument1.ini = 0;
argument1.mid = mid-1;
argument1.fim = size;
argument2.ini = mid;
argument2.mid = size-1;
argument2.fim = size;
pthread_t thread1, thread2;
// thread and execution
pthread_create(amp;thread1, NULL, thread_func, amp;argument1);
printf("done out 1n");
pthread_create(amp;thread2, NULL, thread_func, amp;argument2);
printf("done out 2n");
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
free(v);
return 0;
}
void quicksort (int *v, int ini, int fim, int size){
if (ini >= fim)
return;
int meio = partition(v, ini, fim);
quicksort(v, ini, meio-1, size);
quicksort(v, meio 1, fim, size);
}
int partition (int *v, int ini, int fim){
int i = ini-1, j = ini;
troca(v, (ini fim)/2, fim);
int pivo = v[fim];
for (; j < fim; j)
{
if (v[j] <= pivo)
{
i ;
troca(v, i, j);
}
}
troca(v, i 1, fim);
return i 1; //indice do pivo;
}
void troca (int *v, int i, int j){
int aux = v[i];
v[i] = v[j];
v[j] = aux;
return;
}
Выполнение работает и сортируется отлично, оно генерирует 2 новых независимых потока, которые сортируют половинки массива каждый. Проблема в том, что он не делает это одновременно. Запуск программы с вводом 100 миллионов случайных чисел:
done out 1
done out 2
done in
done in
real 0m47,464s
user 0m50,686s
sys 0m0,452s
Но для появления первых 3 строк требуется около 25 секунд, а для последней — ~ 25, что указывает на то, что второй поток ожидает запуска первого.
В htop
консоли кажется, что в какой-то момент оба выполняются одновременно (это подтверждается тем фактом, что эта программа работает немного быстрее, чем моя обычная)
Наконец, я понимаю, что небезопасно работать одновременно с такого рода данными, но в этом примере сортировки все должно быть в порядке.
Комментарии:
1.
argument1.fim = size;
Должно ли это бытьsize / 2
?2. @JohnnyMopp, этот размер не включается в сортировку и служит только для того, чтобы не превышать границы массива
3. Вы уверены, что запускаете его на компьютере / установке, которая позволяет запускать два потока одновременно?
4. Я уверен, что я работаю на Debian 10 buster на четырехъядерном процессоре intel i5-3337U. И он показывает в течение короткого периода времени все 3 процесса, запущенные на
htop
Ответ №1:
Вы несправедливо распределяете работу между потоками. Чтобы увидеть это, измените свой код следующим образом:
// thread and execution
pthread_create(amp;thread1, NULL, thread_func, amp;argument1);
printf("done out 1 (%d)n", argument1.mid - argument1.ini 1);
pthread_create(amp;thread2, NULL, thread_func, amp;argument2);
printf("done out 2 (%d)n", argument2.mid - argument2.ini 1);
Вы увидите, что на один поток обычно приходится примерно в два раза больше работы, чем на другой.
Например, вот несколько запусков с использованием случайных данных:
выполнено 1 (66474145)
выполнено 2 (33525855)
выполнено 1 (21794872)
выполнено 2 (78205128)
выполнено 1 (67867800)
выполнено 2 (32132200)
Вы никогда не должны делить свою работу на небольшое количество очень больших блоков и назначать каждый блок потоку, если вы заботитесь о параллелизме. Вместо этого создайте очередь небольших задач и позвольте потокам извлекать задачи из очереди по мере их завершения.
Комментарии:
1. Спасибо, возможно, быстрая сортировка не является хорошим примером многопоточной задачи
2. @ACoelho Все в порядке, вы просто не хотите делить работу на потоки в начале, но по мере продвижения, и вы не хотите заставлять какой поток выполняет какую работу, но позволяете реализации находить оптимальное назначение.
3. Сейчас я изучаю многопоточность, так что спасибо за информацию
4. Вот быстрая и грязная исправленная версия: onlinegdb.com/By1GarCCD
Ответ №2:
Потоки выполняются одновременно (ну, не обязательно одновременно, но ощутимо одновременно). 25-секундная задержка, которую вы видите, связана с ошибкой в вашей быстрой сортировке (или, возможно, с тем, как вы делите список между вашими двумя потоками). По сути, потоку 2 поручается гораздо больше работы, чем потоку 1, и поэтому для его завершения требуется гораздо больше времени. Поток 2 не просто выполняется «после» потока 1 или «ожидает» потока 1.
Чтобы доказать это, я добавил unsigned long*
аргумент quicksort
и заставил его увеличивать значение, на которое ссылается указанный указатель при каждом вызове (по сути, подсчитывая количество вызовов каждого потока quicksort
), и я запустил его с 10M (не 100M) случайными значениями. Количество потоков 1 составило 3 851 991, а количество потоков 2 составило 9 693 697. Конечно, могут быть небольшие различия между двумя подсчетами из-за случайности при генерации списка. Но разница составляет почти в 3 раза, что гораздо более значительно, чем вы могли ожидать от небольших случайных изменений.
Я предлагаю вам попробовать другую реализацию quicksort
(ту, которая, как известно, работает). Я также предлагаю быть более осторожным с совместным использованием данных (убедитесь, что два потока никогда не обращаются к данным друг друга, особенно без синхронизации), чтобы получить более точную оценку времени; последнее, что вы хотите, это чтобы один поток сортировал или не сортировал данные другого потока.
Комментарии:
1. Да, быстрая сортировка нелегко разделяется в первом разделе и редко делится на 2 равные части, я попробую еще несколько потоков с более однородным кодом
Ответ №3:
Создание потока является несправедливым: поток # 1 создается до потока # 2. Более того, когда создается поток # 1, он может запускаться и вытеснять основной поток, который может ждать, пока он вернет процессор для создания и запуска потока # 2. Однако поток, выполняющийся в соответствии с политикой SCHED_OTHER по умолчанию, ведет себя непредсказуемо.
Чтобы добавить некоторую предсказуемость:
- Заставьте потоки запускаться одновременно после создания. Используйте барьер, чтобы вызвать «go» для всех потоков одновременно. См. pthread_barrier_init()
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void troca (int *v, int i, int j);
int partition (int *v, int ini, int fim);
void quicksort (int *v, int ini, int fim, int size);
pthread_barrier_t barrier;
typedef struct {
int id;
int ini, mid, fim;
} thread_arg;
int size;
int *v;
void *thread_func(void* arg){
thread_arg *a = arg;
pthread_barrier_wait(amp;barrier);
quicksort(v, a->ini, a->mid, a->fim);
printf("done in %dn", a->id);
return NULL;
}
int main()
{
// initializations
scanf("%d", amp;size);
v = malloc(size * sizeof(int));
// read array
for (int i = 0; i < size; i)
scanf("%d", amp;v[i]);
// arguments
thread_arg argument1, argument2;
int mid = partition(v, 0, size-1);
argument1.id = 1;
argument1.ini = 0;
argument1.mid = mid-1;
argument1.fim = size;
argument2.id = 2;
argument2.ini = mid;
argument2.mid = size-1;
argument2.fim = size;
pthread_t thread1, thread2;
pthread_barrier_init(amp;barrier, NULL, 3);
// thread and execution
pthread_create(amp;thread1, NULL, thread_func, amp;argument1);
printf("done out 1n");
pthread_create(amp;thread2, NULL, thread_func, amp;argument2);
printf("done out 2n");
// Start the threads
pthread_barrier_wait(amp;barrier);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
free(v);
pthread_barrier_destroy(amp;barrier);
return 0;
}
void quicksort (int *v, int ini, int fim, int size){
if (ini >= fim)
return;
int meio = partition(v, ini, fim);
quicksort(v, ini, meio-1, size);
quicksort(v, meio 1, fim, size);
}
int partition (int *v, int ini, int fim){
int i = ini-1, j = ini;
troca(v, (ini fim)/2, fim);
int pivo = v[fim];
for (; j < fim; j)
{
if (v[j] <= pivo)
{
i ;
troca(v, i, j);
}
}
troca(v, i 1, fim);
return i 1; //indice do pivo;
}
void troca (int *v, int i, int j){
int aux = v[i];
v[i] = v[j];
v[j] = aux;
return;
}
- Заставьте потоки выполняться на разных ядрах процессора, чтобы увеличить параллелизм. Cf. pthread_attr_setaffinity_np()
- Сделайте поток в режиме реального времени. См. pthread_attr_setschedpolicy() и pthread_attr_setschedparam().