Я изучаю многопоточность, и ради понимания у меня есть wriiten небольшая функция с помощью многопоточности... это хорошо работает. Но я просто хочу знать, безопасно ли тот поток использовать, сделал я следовал корректному правилу.
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();
}
Как я уже указывал ранее, то, что вы делаете, совершенно бессмысленно, вы также можете не использовать потоки, поскольку вы запускаете поток, а затем ждете завершения потока, прежде чем делать что-нибудь еще.
Вы даете очень мало информации о CEvent, но ваши объекты WaitForSingleObject ждут, пока поток не войдет в сигнальное состояние (т. Е. Завершится).
Поскольку MyCommonFunction - это то место, где происходит фактическая потенциально небезопасная для потоков вещь, вы правильно разбили критическую область на разделы, однако потоки 2 и потоки 3 не выполняются одновременно. Удалите объект WaitForSingleObject из MyThreadFunction3, и тогда они будут работать одновременно в потокобезопасном режиме благодаря критическому разделу.
Тем не менее, это все еще немного бессмысленно, поскольку оба потока будут проводить большую часть своего времени в ожидании освобождения критического раздела.В общем, вы хотите структурировать потоки так, чтобы им было очень мало, чтобы поразить критические секции, а затем, когда они попадают в критическую секцию, воздействовать на нее только на очень короткое время (то есть не большую часть времени обработки функции) .
Править :
A Критическая секция работает, говоря, что я держу эту критическую секцию, все остальное, что хочет, должно подождать. Это означает, что поток 1 входит в критическую секцию и начинает делать то, что ему нужно. Затем появляется поток 2 и говорит: «Я хочу использовать критическую секцию». Ядро сообщает: «Поток 1 использует критическую секцию, которую вы должны дождаться своей очереди». Появляется поток 3, и ему говорят то же самое. Потоки 2 и 3 теперь находятся в состоянии ожидания, ожидая освобождения этой критической секции. Когда поток 1 завершает критическую секцию, потоки 2 и 3 соревнуются, кто первым получит критическую секцию, а когда один получит ее, другой должен продолжить ожидание.
В вашем примере, приведенном выше, будет так много ожидания критических секций, что возможно, что поток 1 может находиться в критической секции, а поток 2 ожидает, и до того, как потоку 2 будет предоставлена возможность войти в критическую секцию, поток 1 имеет сделал петлю и снова вошел в нее. Это означает, что поток 1 может завершить всю свою работу до того, как поток 2 когда-либо получит шанс войти в критическую секцию. Следовательно, поддержание минимального объема работы, выполняемой в критической секции по сравнению с остальной частью цикла / функции, поможет потокам работать одновременно.В вашем примере один поток ВСЕГДА будет ждать другой поток, и, следовательно, простое выполнение этого последовательно может быть быстрее, поскольку у вас нет накладных расходов на потоки ядра.
т.е. чем больше вы избегаете CriticalSections, тем меньше времени теряется для потоков, ожидающих друг друга. Однако они необходимы, поскольку вам НЕОБХОДИМО убедиться, что 2 потока не пытаются одновременно работать с одним и тем же объектом. Некоторые встроенные объекты являются "атомарными" , которые могут помочь вам в этом, но для неатомарных операций критическая секция является обязательной.
Событие - это другой вид объекта синхронизации. По сути, событие - это объект, который может находиться в одном из двух состояний. Включено или нет. Если вы используете WaitForSingleObject для «несигнального» события, то поток будет переведен в спящий режим до тех пор, пока не перейдет в сигнальное состояние.
Это может быть полезно, когда у вас есть поток, который ДОЛЖЕН ждать, пока другой поток что-то завершит. В общем, вы хотите по возможности избегать использования таких объектов синхронизации, поскольку это разрушает параллельность вашего кода.
Лично я использую их, когда у меня есть рабочий поток, ожидающий, когда ему нужно что-то сделать. Поток большую часть времени находится в состоянии ожидания, а затем, когда требуется некоторая фоновая обработка, я сигнализирую о событии. Затем поток оживает и делает то, что ему нужно, перед тем, как вернуться к циклу и снова войти в состояние ожидания. Вы также можете пометить переменную как указывающую, что объект должен выйти. Таким образом, вы можете установить для переменной выхода значение true, а затем сигнализировать ожидающему потоку.Ожидающий поток просыпается и говорит: «Я должен выйти», а затем завершается. Однако имейте в виду, что вам «может потребоваться» барьер памяти , который говорит, что убедитесь, что переменная выхода установлена до того, как событие будет разбужено, иначе компилятор может изменить порядок операций.Это может привести к тому, что ваш поток проснется, обнаружив, что переменная выхода не настроена для выполнения своей задачи, а затем вернется в спящий режим. Однако поток, который первоначально послал сигнал, теперь предполагает, что поток завершился, хотя на самом деле это не так.
Кто сказал, что многопоточность - это просто, а? ;)
Какая функция, по вашему мнению, должна быть "потокобезопасной"?
Я думаю, что этот термин следует применить к вашей 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);
все остальные переменные локальны, находятся в стеке и принадлежат текущим потокам - так что вам просто нужно обратиться к документации по этим коллекциям, чтобы определить, могут ли они безопасно вызываться отдельными потоками.
Похоже, это сработает, потому что потоки не будут выполнять какую-либо работу одновременно. Если вы измените код, чтобы потоки выполняли работу одновременно, вам нужно будет поместить мьютексы (в MFC вы можете использовать для этого CCriticalSection
) вокруг кода, который обращается к элементам данных, которые используются совместно потоки.