#c #multithreading
Вопрос:
#include lt;stdio.hgt; #include lt;stdlib.hgt; #include lt;sys/types.hgt; #include lt;dirent.hgt; #include lt;pthread.hgt; #include lt;fcntl.hgt; #include lt;unistd.hgt; #include lt;sys/stat.hgt; #include lt;sys/mman.hgt; #include lt;string.hgt; void* copyFile(void* arg) { char *filename = ((char*)arg); char *destname = strcat(filename, "copy"); int in = 0; in = open(filename, O_RDONLY); int out = 0; out = open(destname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); struct stat statbuf; fstat(in, amp;statbuf); off_t insize = statbuf.st_size; lseek(out, insize - 1, SEEK_SET); ssize_t wrote = write(out, "", 1); char* inmap = mmap(0, insize, PROT_READ, MAP_FILE | MAP_PRIVATE, in, 0); char* outmap = mmap(0, insize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, out, 0); memcpy(outmap, inmap, insize); close(out); } int main(int argc, char *argv[]) { DIR* d; struct dirent* e; d = opendir(argv[1]); int num_entries = 0; int i = 0; pthread_t threads[50]; while((e = readdir(d)) != NULL) { char *filename = e-gt;d_name; printf("%sn", filename); pthread_create(amp;threads[i], NULL, copyFile, filename); i ; } for(int j = 0; j lt; i; j ) { pthread_join(threads[j], NULL); } }
Здравствуйте, я пытаюсь использовать mmap и memcpy для копирования всего каталога файлов с использованием многопоточности. У меня есть код для копирования файлов в каталог, однако он заставляет все СКОПИРОВАННЫЕ файлы иметь размер 1 байт. Мне любопытно, почему это происходит и что можно сделать для устранения этой ошибки.
Я очень новичок в C, поэтому, пожалуйста, простите мои очевидные семантические ошибки. Я ценю вашу помощь!
Комментарии:
1. Вам нужно сделать что-то вроде «сначала прочитайте», а затем «читайте дальше»? Кроме того, вы изменяете каталог по мере чтения записей из него?
2. У меня сложилось впечатление, что цикл while повторяется в потоке каталогов и, следовательно, не потребуется «сначала прочитать» или «читать дальше». Кроме того, я не изменяю каталог, насколько мне известно.
3.
readdir
возвращает указатель на структуру, которая может быть статически выделена. Это означает, что вы можете передавать одно и то же значениеfilename
во все потоковые экземплярыcopyFile
.4.@G. M. Например, если бы у нас было 3 файла
f1
f2
f3
, ожидали бы мы, что на выходе будет 3 экземпляраf1copy
в соответствии с предложенной вами ошибкой? Если это так, то, к сожалению, это не то, что я испытываю, все имена указаны правильно (хотя я надеюсь, что просто неправильно понимаю ваше объяснение, и вы правы!).5. Также
strcat
изменяет передаваемую строку. Так что еслиfilename
было"foo"
, тоchar *destname = strcat(filename, "copy");
это приводит кfilename
тому, что вы являетесьfoocopy
иdestname
являетесь еще одним указателем на ту же строку. И в любом случае,filename
это указатель на возвращаемый буферreaddir
, который вы не должны изменять (особенно потому, что он хранится в других потоках). Вам нужно создать временный буфер для храненияdestname
.
Ответ №1:
Есть ряд ошибок.
- В
main
,filename
будет указывать на один и тот же адрес для всех файлов. Мы должны предоставить уникальный адрес/копию. Один из способов-использоватьstrdup
. И мы должны добавитьfree
вызов в нижней частиcopyFile
, чтобы освободить хранилище, выделенноеstrdup
. - Мы должны пропустить
.
и..
при копировании. В противном случае код сегментируется. - Использование одного и того же каталога для входных файлов и копии может привести к эквивалентному бесконечному циклу. То есть, учитывая файл
xyz
, в который копируетсяxyzcopy
,readdir
он может быть виденxyzcopy
в качестве входных данных и попытаться создатьxyzcopycopy
. Лучше использовать отдельный выходной каталог. Это потребовало бы серьезной переписки. Но более простое решение-пропустить любой файл, который содержитcopy
. - Программа [попытается] скопировать записи каталога, даже если они не являются обычными файлами (например, она попытается сделать
open
это в каталоге). Мы должны проверить тип файла и пропустить типы, отличные от файлов. - В
copyFile
, даже сstrdup
помощью inmain
, в строкеfilename
недостаточно места, чтобы разрешитьstrcat
из"copy"
. Нам нужен отдельный буфер для выходного имени файла. lseek/write
Добавляет лишний двоичный ноль в конце выходного файла. Это делается для расширения выходного файла. Лучше использоватьftruncate
для этого.- Это потому, что смешивание
write
сmmap
может быть проблематичным и обычно не является лучшей практикой. В вашем случае использования это может быть нормально (потомуwrite
что это делается доmmap
), но если мы возьмем на себя труд использоватьmmap
, лучше просто использовать указательmmap
буфера иmemcpy
. copyFile
отсутствует котreturn
. И,main
также отсутствуетreturn
- Здесь нет
close
in
никакого «заcopyFile
«. - При
open
mmap
вызовах , , проверка ошибок неopendir
выполняется. - До звонков должны быть
munmap
close
звонки.
Ошибки не исправлены:
- Программа ограничена количеством записей, которые могут содержаться в каталоге.
- С
pthread_t threads[50];
помощью мы можем иметь только 50 записей в каталоге. Проверка переполнения этого массива отсутствует. - Нет практического смысла делать все файлы параллельно. Это не масштабируется.
- Если бы у нас было (например) 1 000 000 записей в каталоге, у нас могли бы закончиться ресурсы. У нас могут закончиться неиспользуемые слоты для процессов, и
pthread_create
они начнут выходить из строя после достижения общесистемного предела. У нас могут закончиться файловые дескрипторы, иopen
вызовы начнут завершаться сбоем примерно после 1024 открытых файлов. - После создания заданного количества потоков (например, на практике 4-5) процесс копирования на самом деле будет медленнее, поскольку система будет тратить большую часть своего времени на переключение между потоками, а не на выполнение полезной работы.
- Для небольших файлов накладные расходы на создание/удаление потока становятся значительными по сравнению со временем выполнения потока.
Было бы лучше иметь ограниченный «пул» «рабочих» потоков и чередовать pthread_create
вызовы с pthread_join
вызовами, возвращая/повторно используя теперь неиспользуемый/свободный «слот», когда поток завершается.
Более эффективным способом было бы создать пул потоков (с помощью pthread_create
вызовов один раз в начале. У каждого потока будет свой почтовый ящик/очередь для выполнения работы. main
может ставить записи в очередь в потоки, добавляя новую запись, когда видит, что поток простаивает. pthread_join
Звонки можно было сделать один раз в конце
Ниже приведен исправленный код.
Я использовал условия препроцессора для обозначения старого и нового кода (например):
#if 0 // old code #else // new code #endif #if 1 // new code #endif
В любом случае, вот исправленный код. Я прокомментировал ошибки/исправления комментариями:
#include lt;stdio.hgt; #include lt;stdlib.hgt; #include lt;sys/types.hgt; #include lt;dirent.hgt; #include lt;pthread.hgt; #include lt;fcntl.hgt; #include lt;unistd.hgt; #include lt;sys/stat.hgt; #include lt;sys/mman.hgt; #include lt;string.hgt; void * copyFile(void *arg) { // NOTE/BUG: no cast is needed from a void * #if 0 char *filename = ((char *) arg); #else char *filename = arg; #endif // NOTE/BUG: there is _not_ enough space in filename to add "copy" -- this is // UB (undefined behavior) #if 0 char *destname = strcat(filename, "copy"); #else char destname[1024]; strcpy(destname,filename); strcat(destname,"copy"); #endif int in = 0; in = open(filename, O_RDONLY); // NOTE/FIX: check for error #if 1 if (in lt; 0) { perror(filename); exit(1); } #endif int out = 0; out = open(destname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); // NOTE/FIX: check for error #if 1 if (out lt; 0) { perror(destname); exit(1); } #endif struct stat statbuf; fstat(in, amp;statbuf); off_t insize = statbuf.st_size; // NOTE/BUG: this will add a binary zero at the end of the output file #if 0 lseek(out, insize - 1, SEEK_SET); ssize_t wrote = write(out, "", 1); #else ftruncate(out,insize); #endif char *inmap = mmap(0, insize, PROT_READ, MAP_FILE | MAP_PRIVATE, in, 0); // NOTE/FIX: check for error #if 1 if (inmap == MAP_FAILED) { perror("mmap/in"); exit(1); } #endif char *outmap = mmap(0, insize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, out, 0); // NOTE/FIX: check for error #if 1 if (inmap == MAP_FAILED) { perror("mmap/in"); exit(1); } #endif memcpy(outmap, inmap, insize); // NOTE/FIX: we should unamp the output area #if 1 munmap(outmap,insize); #endif close(out); // NOTE/FIX: we should unmap and close the input descriptor #if 1 munmap(inmap,insize); close(in); #endif // NOTE/FIX: free the string allocated by strdup in main #if 1 free(filename); #endif // NOTE/FIX: needs return value -- would be flagged by compiler if compiled // with -Wall #if 1 return (void *) 0; #endif } int main(int argc, char *argv[]) { DIR *d; struct dirent *e; // NOTE/BUG: no checks for missing argument or bad directory #if 0 d = opendir(argv[1]); #else if (argv[1] == NULL) { fprintf(stderr,"missing argumentn"); exit(1); } d = opendir(argv[1]); if (d == NULL) { perror(argv[1]); exit(1); } #endif // NOTE/BUG: unused variable #if 0 int num_entries = 0; #endif int i = 0; pthread_t threads[50]; while ((e = readdir(d)) != NULL) { // NOTE/FIX: must skip "." and ".." #if 1 if (strcmp(e-gt;d_name,".") == 0) continue; if (strcmp(e-gt;d_name,"..") == 0) continue; #endif // NOTE/FIX: only copy simple files (i.e. do _not_ copy subdirectories, etc.) #if 1 if (e-gt;d_type != DT_REG) continue; #endif // NOTE/FIX: skip "copy" files #if 1 if (strstr(e-gt;d_name,"copy") != NULL) continue; #endif // NOTE/BUG: this will present the _same_ memory address to all threads so there // is a "race condition" #if 0 char *filename = e-gt;d_name; #else char *filename = strdup(e-gt;d_name); #endif printf("%sn", filename); pthread_create(amp;threads[i], NULL, copyFile, filename); i ; } for (int j = 0; j lt; i; j ) { pthread_join(threads[j], NULL); } // NOTE/FIX: needs return #if 1 return 0; #endif }
Комментарии:
1. Спасибо вам за очень подробный ответ! Это сработало!