- •1. Введение
- •2. Многопоточное программирование
- •3.1. Example1: работа с памятью
- •3.1.1. Производительность
- •3.1.2. Масштабируемость
- •3.1.3. Масштабируемость Hyper-Threading архитектур
- •3.2. Example2.Exe: работа с файлами
- •4. Многопоточные программы
- •4.1. Mtftext.Exe: учебный пример
- •4.2. Mtcksrc.Exe: проверка исходного кода
- •If (tout.Size()) file(mp, fd::out).Write(tout);
- •4.3. Mtdel.Exe: удаление файлов
- •Вывод итоговой статистики, т.Е. Общего количества удаленных файлов и их совокупного размера.
- •Сообщение о возникших ошибках, как последнее сообщение программы.
- •4.4. Mtcnvsrc.Exe: конвертация исходного кода
- •4.5. Mtdirdiff.Exe: сравнение директорий
- •Mem_pool ownmp;
- •5. Библиотека derslib
- •6. Заключение
Структура DirCont использует свой собственный пул для имен директорий и файлов:
struct DirCont {
Mem_pool ownmp;
sh_text name;
hash_vec<sh_text, char> dirs;
hash_vec<sh_text, unsigned long> files;
DirCont(const ch_rng& nm) : name(nt(ownmp, nm)), dirs(101), files(1001)
{}
};
Здесь мы имеем тот редкий случай, когда в одной точке приложения одновременно сходятся несколько объектов mem_pool, принадлежащих разным потокам:
объект ownmp, принадлежащий соответствующей структуре mt.oldDir или mt.newDir
привычный объект mp, являющийся временным личным объектом рабочего потока, создаваемым thread_pool-ом на время работы функции MainTask::proc()
Как следствие, doFindFiles() создает сохраняемые имена директорий и файлов с помощью ownmp:
void MainTask::doFindFiles(mem_pool& mp, data_queue&, Arg&, const FindFilesMsg&
msg)
{
int pref=msg.dir->name->size();
vector<sh_text> dirs;
for (dirs.push_back(msg.dir->name); dirs.size(); ) {
sh_dir shd=new_dir(mp, dirs.back());
dirs.pop_back();
for (dir::entry dent(mp); shd->find_next(dent); ) {
if (dent.name=="." || dent.name=="..") continue;
sh_text fname=shd->full_name(dent);
if (dent.isdir) dirs.push_back(fname);
assert(fname->size()>=pref+1);
int beg= (fname->begin()[pref]==pathSepr) ? pref+1 : pref;
sh_text name(nt(msg.dir->ownmp, fname->begin()+beg, fname->end()));
if (dent.isdir) msg.dir->dirs.insert(name, 0);
else msg.dir->files.insert(name, dent.size);
}
}
}
А doCompare() и doCopyFile() запоминают найденные имена (факт. ключи hash_vec<sh_text, ...>-ра) по ссылке, а не значению:
const sh_text& dir=dc1->dirs.key(i);
// ...
const sh_text& fil=dc1->files.key(i);
// ...
const sh_text& name=oldDir.files.key(msg.oldPos);
Т.к. сохранение по значению приведет к копированию объекта sh_ptr<text> и, как следствие, одновременному использованию ownmp несколькими рабочими потоками. Тем самым в программе появится ошибка синхронизации, т.е. та самая "великая и ужасная" race condition, непонятно когда и как проявляющаяся.
И если желаете, то в качестве неприятного, но весьма поучительного упражнения можно и в самом деле заменить ссылки на значения и попытаться ее отследить...
Изменившийся файл может находиться достаточно глубоко в структуре сравниваемых поддиректорий, поэтому его копирование в директорию diff_dir/mod/... может потребовать предварительного создания недостающих промежуточных поддиректорий. Задача осложняется еще и тем, что подлежащие созданию поддиректории могут быть созданы параллельно работающими потоками в промежутке между временем обнаружения их отсутствия и попыткой их создать. Для эффективного решения данной проблемы приложение пробует сразу же создать несуществующий файл и только в случае ошибки выполняется попытка создания промежуточных директорий и повторная попытка создания файла:
file fout(mp);
if (!fout.open(msg.toName, file::wro, file::crt|file::trnc)) {
make_dirs(mp, get_path(mp, msg.toName));
fout.ex_open(msg.toName, file::wro, file::crt|file::trnc);
}
Обратите внимание, что для повторного создания файла вместо функции open() используется вызов ex_open(), автоматически возбуждающей исключения.