题目:
试验:即使把threadfunc改成下面的代码,即让SetEvent和
WaitForSingleObject有个不确定的间隔,跑了二十多下,没有死锁,
结果也正确。
有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四 个文件呈如下格式:A:1 2 3 4 1 2....B:2 3 4 1 2 3....C:3 4 1 2 3 4....D:4 1 2 3 4 1....请设计程序。
解答:
我的思路是这样的:
当每个子线程准备就绪后,主线程发出前进一步的命令【举旗】。这时每个子线程都收到该命令,找到正确的文件,打印自己的编号。然后向主线程报告自己已经完成任务,准备接受主线程的下一个命令。
这里的要点是每个子线程收到命令【举旗】后,该命令必须结束【放旗】。否则子线程完成该命令后会继续执行该命令。
第一次尝试:
使用自动重置事件(Event)作为主线程的旗子,举旗后,等待的四个线程跑起来。
但是这种方案不行,因为任何一个线程跑起来之后,旗子就放下了,导致其他线程跑不起来。
如果使用手工重置事件作为主线程的旗子,又找不到合适的机会放下旗子。
第二次尝试:
把Event改成Semaphore,让4个等待线程可以跑。尝试成功。
#include
<Windows.h>
#include
<iostream>
#include
<fstream>
#include
<process.h>
using
namespace
std;
const
int
thread_count=4;
HANDLE hGo;
//semaphore, used by main thread to notify all writer threads to go one step, like a traffic light
HANDLE hRoundDone[thread_count];
//
Automatic Reset Event,
used by writer thread to notify main thread to show it finished this step
ofstream files[thread_count];
int
round=0;
const
int
max_round = 10;
void
threadfunc(
void
* pi)
{
int
i = *((
int
*)pi);
int
fileId = (i+3)%thread_count;
while
(round < max_round)
{
SetEvent(hRoundDone[i-1]);
WaitForSingleObject(hGo, INFINITE);
files[fileId] << i <<
" "
;
fileId = (fileId+3)%thread_count;
}
}
int
main()
{
hGo = CreateSemaphore(
nullptr
, 0, thread_count,
nullptr
);
int
values[thread_count] = {1,2,3,4};
const
char
* filename[thread_count] = {
"A.txt"
,
"B.txt"
,
"C.txt"
,
"D.txt"
};
for
(
int
i=0; i<thread_count; ++i)
{
hRoundDone[i] = CreateEvent(
nullptr
, FALSE, FALSE,
nullptr
);
files[i].open(filename[i], ios_base::trunc);
_beginthread(threadfunc, 0, values+i);
}
for
(; round<max_round; ++round)
{
cout <<
"Main thread is waiting..."
<< endl;
// wait for all writer thread are ready
WaitForMultipleObjects(thread_count, hRoundDone, TRUE, INFINITE);
ReleaseSemaphore(hGo, thread_count, NULL);
// increase count by thread_count
cout <<
"round="
<< round << endl;
}
CloseHandle(hGo);
for
(
int
i=0; i<4; ++i)
CloseHandle(hRoundDone[i]);
return
0;
}
但是我有一个担心,当举旗时,4个线程至少有一个线程不在等待该旗子时【因为线程调度不可预知】,这样某个线程在某次命令中会不会多跑一圈?而那个掉队的线程就跑不了,导致死锁。
试验:把threadfunc改成下面的代码,即让SetEvent和
WaitForSingleObject有个不确定的间隔。跑了七八下,
结果都正确。但是再跑一下,死锁真的发生了。所以这个现象是比较难以重复的。
#include
<time.h>
void
threadfunc(
void
* pi)
{
srand( (
unsigned
)time( NULL ) );
int
i = *((
int
*)pi);
int
fileId = (i+3)%thread_count;
while
(round < max_round)
{
SetEvent(hRoundDone[i-1]);
Sleep(rand()%100);
WaitForSingleObject(hGo, INFINITE);
files[fileId] << i <<
" "
;
fileId = (fileId+3)%thread_count;
}
}
第三次尝试:
把Semaphore改成4个自动重置事件(Event)。打个比方,四个车道,每个车道一个车(线程),每个车道有自己的红绿灯(自动重置事件),灯变绿,车子跑起来,并且灯立即变红。这样不会出现某个车跑2圈,而另外的某车跑不了的情况。
尝试也成功了。但我认为这个可靠多了。缺点是多用了一些同步对象。但正确性总是第一位的。
#include
<Windows.h>
#include
<iostream>
#include
<fstream>
#include
<process.h>
using
namespace
std;
const
int
thread_count=4;
HANDLE hGo[4];
//Automatic Reset Event, used by main thread to notify all writer threads to go one step, like a traffic light
HANDLE hRoundDone[thread_count];
//Automatic Reset Event, used by writer thread to notify main thread to show it finished this step
ofstream files[thread_count];
int
round=0;
const
int
max_round = 10;
void
threadfunc(
void
* pi)
{
int
i = *((
int
*)pi);
int
fileId = (i+3)%thread_count;
while
(round < max_round)
{
SetEvent(hRoundDone[i-1]);
WaitForSingleObject(hGo[i-1], INFINITE);
files[fileId] << i <<
" "
;
fileId = (fileId+3)%thread_count;
}
}
int
main()
{
int
values[thread_count] = {1,2,3,4};
const
char
* filename[thread_count] = {
"A.txt"
,
"B.txt"
,
"C.txt"
,
"D.txt"
};
for
(
int
i=0; i<thread_count; ++i)
{
hGo[i] = CreateEvent(
nullptr
, FALSE, FALSE,
nullptr
);
hRoundDone[i] = CreateEvent(
nullptr
, FALSE, FALSE,
nullptr
);
files[i].open(filename[i], ios_base::trunc);
_beginthread(threadfunc, 0, values+i);
}
for
(; round<max_round; ++round)
{
cout <<
"Main thread is waiting..."
<< endl;
// wait for all writer thread are ready
WaitForMultipleObjects(thread_count, hRoundDone, TRUE, INFINITE);
for
(
int
i=0; i<thread_count; ++i)
{
SetEvent(hGo[i]);
}
cout <<
"round="
<< round << endl;
}
for
(
int
i=0; i<4; ++i)
{
CloseHandle(hGo[i]);
CloseHandle(hRoundDone[i]);
}
return
0;
}
#include
<time.h>
void
threadfunc(
void
* pi)
{
srand( (
unsigned
)time( NULL ) );
int
i = *((
int
*)pi);
int
fileId = (i+3)%thread_count;
while
(round < max_round)
{
SetEvent(hRoundDone[i-1]);
Sleep(rand()%100);
WaitForSingleObject(hGo[i-1], INFINITE);
files[fileId] << i <<
" "
;
fileId = (fileId+3)%thread_count;
}
}
跨平台的实现:
#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include <condition_variable>
const int thread_count = 4;
bool go[thread_count] = { false, false, false, false };
std::mutex mtx;
std::condition_variable cv;
std::ofstream files[thread_count];
const int max_round = 10;
void threadfunc(int i)
{
int fileId = i;
for (int this_round = 0; this_round<max_round; ++this_round)
{
{
std::unique_lock<std::mutex> lck(mtx);
while (!go[i - 1])
cv.wait(lck);
go[i - 1] = false;
}
fileId = (fileId + 3) % thread_count;
files[fileId] << i << " ";
}
}
int main()
{
const char* filename[thread_count] = { "A.txt", "B.txt" , "C.txt" , "D.txt" };
std::thread threads[thread_count];
for (int i = 0; i<thread_count; ++i)
{
files[i].open(filename[i], std::ios_base::trunc);
threads[i] = std::thread(threadfunc, i + 1);
}
for (int this_round = 0; this_round<max_round; ++this_round)
{
std::cout << "Main thread is waiting..." << std::endl;
// wait for all writer thread are ready
while (go[0] || go[1] || go[2] || go[3]) {
std::this_thread::yield();
}
{
std::unique_lock<std::mutex> lck(mtx);
go[0] = true;
go[1] = true;
go[2] = true;
go[3] = true;
cv.notify_all();
}
std::cout << "this_round=" << this_round << std::endl;
}
for (int i = 0; i<thread_count; ++i)
{
threads[i].join();
}
return 0;
}