题目:
试验:即使把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;
}
多线程文件写入同步

1828

被折叠的 条评论
为什么被折叠?



