Как правильно спроектировать рабочий поток?(избегайте, например, Sleep (1)) - PullRequest
2 голосов
/ 05 июля 2011

Я все еще новичок в многопоточности, поэтому потерпите меня, пожалуйста:

В настоящее время я пишу приложение, которое выполняет вычисления FVM в сетке.это модель с явным временем, поэтому на каждом временном шаге мне нужно вычислять новые значения для всей сетки.моя идея состояла в том, чтобы распределить это вычисление на 4 рабочих потока, которые затем обрабатывают ячейки сетки (первый поток вычисляет 0, 4, 8 ... второй поток 1, 5, 9 ... и т. д.).

я создаю эти 4 потока при запуске программы.

они выглядят примерно так:

void __fastcall TCalculationThread::Execute()
{
    bool alive = true;
    THREAD_SIGNAL ts;
    while (alive)
    {
        Sleep(1);
        if (TryEnterCriticalSection(&TMS))
        {
        ts = thread_signal;
        LeaveCriticalSection(&TMS);
        alive = !ts.kill;
        if (ts.go && !ts.done.at(this->index))
        {
            double delta_t = ts.dt;
            for (unsigned int i=this->index; i < cells.size(); i+= this->steps)
            {
                    calculate_one_cell();
            }
            EnterCriticalSection(&TMS);
                thread_signal.done.at(this->index)=true;
            LeaveCriticalSection(&TMS);
        }
    }
}

они используют глобальную структуру для связи с основным потоком (основным потоком)устанавливает ts.go в значение true, когда работникам нужно начинать.

Теперь я уверен, что это не способ сделать это! Мало того, что это не правильно, но и не очень хорошо ...

я прочитал, например, здесь , что семафор или событие будет работать лучше. Ответ на вопрос этого парня говорит о очереди без блокировки.

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

Спасибо за ваше время. (и извините за форматирование)

Я использую Borland C ++ Builder и его поток-объект (TThread).

Ответы [ 2 ]

2 голосов
/ 05 июля 2011

Вызов Sleep(1) в цепочке вычислений не может быть хорошим решением любой проблемы. Вы хотите, чтобы ваши потоки выполняли полезную работу, а не блокировали без веской причины.

Я думаю, что ваша основная проблема может быть выражена в виде последовательного алгоритма этой базовой формы:

for (int i=0; i<N; i++)
    cells[i]->Calculate();

Вы находитесь в счастливом положении, когда звонки на Calculate() независимы друг от друга - здесь у вас есть параллель. Это означает, что вы можете реализовать это без мьютекса.

Существует множество способов добиться этого. OpenMP будет один; нить пул класс другой. Если вы собираетесь развернуть собственное решение на основе потоков, используйте InterlockedIncrement() для общей переменной, чтобы перебрать массив.

Вы можете столкнуться с некоторыми ложными проблемами обмена, как предполагает @DeadMG, но вполне возможно, что нет. Если у вас есть ложное разделение, тогда еще один подход состоит в том, чтобы пройти через большие подмассивы. По существу, приращение (то есть шаг), переданное InterlockedIncrement(), будет больше единицы.

Суть в том, что способ сделать код быстрее - это удалить как критическую секцию (и, следовательно, конфликт на ней), так и Sleep(1).

2 голосов
/ 05 июля 2011

Определенно более эффективным алгоритмом было бы вычисление выходов для 0,1,2,3 в одном потоке, 4,5,6,7 в другом и т. Д. Чередование обращений к памяти, как это, очень плохо, даже если переменныеполностью независимы - вы получите ложные проблемы обмена.Это эквивалент блокировки процессора при каждой записи .

...