Эта функция потокобезопасна? - PullRequest
1 голос
/ 05 мая 2010

Я изучаю многопоточность и ради понимания я написал небольшую функцию, использующую многопоточность ... она отлично работает. Но я просто хочу знать, безопасно ли использовать этот поток, следовал ли я правильному правилу .

void CThreadingEx4Dlg::OnBnClickedOk()
{
    //in thread1 100 elements are copied to myShiftArray(which is a CStringArray)
    thread1 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction1,this);
    WaitForSingleObject(thread1->m_hThread,INFINITE);
    //thread2 waits for thread1 to finish because thread2 is going to make use of myShiftArray(in which thread1 processes it first)
    thread2 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction2,this);
    thread3 = AfxBeginThread((AFX_THREADPROC)MyThreadFunction3,this);

}

UINT MyThreadFunction1(LPARAM lparam)
{
    CThreadingEx4Dlg* pthis = (CThreadingEx4Dlg*)lparam;
    pthis->MyFunction(0,100);
    return 0;
}
UINT MyThreadFunction2(LPARAM lparam)
{
    CThreadingEx4Dlg* pthis = (CThreadingEx4Dlg*)lparam;
    pthis->MyCommonFunction(0,20);
    return 0;
}

UINT MyThreadFunction3(LPARAM lparam)
{
    CThreadingEx4Dlg* pthis = (CThreadingEx4Dlg*)lparam;
    WaitForSingleObject(pthis->thread3->m_hThread,INFINITE);
    //here thread3 waits for thread 2 to finish so that thread can continue
    pthis->MyCommonFunction(21,40);
    return 0;
}
void CThreadingEx4Dlg::MyFunction(int minCount,int maxCount)
{

    for(int i=minCount;i<maxCount;i++)
    {
        //assume myArray is a CStringArray and it has 100 elemnts added to it.
        //myShiftArray is a CStringArray -public to the class
        CString temp;
        temp = myArray.GetAt(i);
        myShiftArray.Add(temp);
    }

}

void CThreadingEx4Dlg::MyCommonFunction(int min,int max)
{
    for(int i = min;i < max;i++)
    {
        CSingleLock myLock(&myCS,TRUE);
        CString temp;
        temp = myShiftArray.GetAt(i);
        //threadArray is CStringArray-public to the class
        threadArray.Add(temp);
    }
    myEvent.PulseEvent();

}

Ответы [ 3 ]

2 голосов
/ 05 мая 2010

Какую функцию вы собираетесь "поточно-ориентированной"?

Я думаю, что этот термин должен применяться к вашей CommonFunction. Это функция, которую вы намереваетесь вызывать из нескольких (в первом случае двух) потоков.

Я думаю, что ваш код имеет правило в строках:

Thread 2 do some work

meanwhile Thread 3 wait until Thread 2 finishes then you do some work

На самом деле ваш код имеет

WaitForSingleObject(pthis->thread3->m_hThread,INFINITE);

может ждет не ту нить?

Но вернемся к безопасности потока. Где контроль безопасности? Это в логике контроля ваших потоков. Предположим, у вас было много тем, как бы вы расширили написанное? У вас много логики:

if thread a has finished and thread b has finished ...

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

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

В последнем случае единственный вопрос заключается в том, являются ли доступ к myArray и myShiftArray коллекциями, ориентированными на многопотоковое исполнение

    temp = myArray.GetAt(i);
    myShiftArray.Add(temp);

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

0 голосов
/ 05 мая 2010

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

Вы предоставляете очень мало информации о вашем CEvent, но ваши объекты WaitForSingleObject ожидают, когда поток войдет в сигнальное состояние (т. Е. Они выйдут).

Поскольку MyCommonFunction - это место, где происходит потенциально небезопасный поток, вы правильно критически разбили область, однако потоки 2 и потоки 3 не работают одновременно. Удалите WaitForSingleObject из MyThreadFunction3, и тогда у вас оба будут работать одновременно потокобезопасным образом, благодаря критическому разделу.

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

Редактировать :

A Критическая секция работает, говоря, что я держу эту критическую секцию все, что хочет, чтобы оно ожидало. Это означает, что поток 1 входит в критическую секцию и начинает делать то, что ему нужно. Затем появляется поток 2 и говорит: «Я хочу использовать критический раздел». Ядро говорит, что «Поток 1 использует критическую секцию, которую вы должны дождаться своей очереди». Тема 3 приходит и получает то же самое. Потоки 2 и 3 теперь находятся в состоянии ожидания, ожидая освобождения этого критического раздела. Когда Нить 1 заканчивается критической секцией, обе Нити 2 и 3 мчатся, чтобы увидеть, кто первым держит критическую секцию, а когда одна получает ее, другая должна продолжить ожидание.

Теперь в вашем примере, приведенном выше, было бы так много ожидающих критических секций, возможно, что Поток 1 может быть в критической секции, а Поток 2 - в ожидании, прежде чем Потоку 2 будет предоставлена ​​возможность войти в критическую секцию зациклился и снова вошел в него. Это означает, что поток 1 может в конечном итоге выполнить всю свою работу до того, как поток 2 сможет получить доступ к критическому разделу. Поэтому поддержание объема работы, выполняемой в критической секции, по сравнению с остальной частью цикла / функции как можно ниже, поможет одновременно работать потокам. В вашем примере один поток ВСЕГДА будет ожидать другого потока и, следовательно, простое последовательное выполнение этого может на самом деле быть быстрее, поскольку у вас нет накладных расходов на многопоточность ядра.

т.е. чем больше вы избегаете CriticalSections, тем меньше времени теряется для потоков, ожидающих друг друга. Однако они необходимы, так как вам необходимо убедиться, что 2 потока не пытаются одновременно работать с одним и тем же объектом. Некоторые встроенные объекты «атомарные» , которые могут помочь вам в этом, но для неатомарных операций критический раздел является обязательным.

An Событие - это другой тип объекта синхронизации. По сути, событие - это объект, который может быть одним из двух состояний. Сигнализируется или не сигнализируется. Если вы подождете WaitForSingleObject для события «not-signalled», то поток будет переведен в спящий режим до тех пор, пока он не войдет в сигнальное состояние.

Это может быть полезно, когда у вас есть поток, который ДОЛЖЕН ждать, пока другой поток что-то завершит. В общем, хотя вы хотите избегать использования таких объектов синхронизации в максимально возможной степени, поскольку это разрушает параллельность вашего кода.

Лично я использую их, когда у меня есть рабочий поток, ожидающий, когда ему нужно что-то сделать. Поток находится в состоянии ожидания большую часть своего времени, а затем, когда требуется некоторая фоновая обработка, я сигнализирую об этом событии. Затем поток переходит к жизни и делает то, что ему нужно сделать, прежде чем вернуться назад и снова войти в состояние ожидания. Вы также можете пометить переменную как указание, что объект должен выйти. Таким образом, вы можете установить выходную переменную в true и затем сигнализировать ожидающему потоку. Ожидающая нить просыпается и говорит «Я должен выйти», а затем выходит. Имейте в виду, однако, что вам «может» понадобиться барьер памяти , который говорит, что убедитесь, что переменная выхода установлена, прежде чем событие проснется, иначе компилятор может переупорядочить операции. Это может привести к тому, что ваш поток проснется, обнаружив, что переменная выхода не настроена, выполняет свою задачу и затем возвращается в спящий режим. Однако поток, который первоначально отправил сигнал, теперь предполагает, что поток завершился, хотя на самом деле этого не произошло.

Кто бы сказал, что многопоточность была легкой, а? ;)

0 голосов
/ 05 мая 2010

Похоже, что это сработает, потому что потоки не будут выполнять какую-либо работу одновременно. Если вы измените код, чтобы потоки выполняли работу одновременно, вам нужно будет установить взаимные исключения (в MFC вы можете использовать CCriticalSection для этого) вокруг кода, который обращается к элементам данных, которые совместно используются потоками.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...