从另一个线程终止C ++中的线程

时间:2012-02-27 22:55:41

标签: c++ windows multithreading primes

基本上我找到两个线程的素数。我为每个线程将可能的素数范围分成两半,或以静态方式分配线程之间的范围。然而,必须处理较小数字的线程将不可避免地在计算较大数字的线程之前完成。我想要做的是,一旦任一线程进入其范围,终止两个线程,然后将未完成的线程范围的其余部分提供给那个完成的线程范围的一半,以便它们递归均匀并且将始终并行运行。 例如:A(1-100)和B(100-200),A先完成,而B仍然是150.两个停止,A开始像A(150-175),B开始像B(175-200)。

到目前为止,这是我的代码:

#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <queue>
#include <cmath>
#include <iostream>
using namespace std;
priority_queue<int> primes;
CRITICAL_SECTION critical;
struct args
{
    int begin;
    int end;
}par1, par2;

int e_prosto(int n)
{
    for(int i = 2; i*i<(n + 1) ; i++)
    if (n & 1 == 0 || n % i == 0) return 0;
    return 1;
}

unsigned int __stdcall rabotnik(void* n)
{
    struct args *lPar = (args*) n;
    for(int i = lPar->begin; i < lPar->end; i++)
    {
        if(e_prosto(i)){
            EnterCriticalSection(&critical);
            primes.push(i);
            LeaveCriticalSection(&critical);
        }
    }
    return 0;
}

int main()
{
    int foo;
    printf(" Tarsene na prosti do: ");
    scanf("%d", &foo);
    par1.begin=1;
    par1.end=foo/2+1;
    par2.begin=foo/2+1;
    par2.end=foo;

    HANDLE hvadkaA, hvadkaB;
    InitializeCriticalSection(&critical);
    SYSTEMTIME st, now, then;

    hvadkaA = (HANDLE)_beginthreadex(0, 0, &rabotnik, (void*)&par1, 0, 0);
    hvadkaB = (HANDLE)_beginthreadex(0, 0, &rabotnik, (void*)&par2, 0, 0);
    ::GetSystemTime(&then);
    WaitForSingleObject(hvadkaA, INFINITE);
    WaitForSingleObject(hvadkaB, INFINITE);

    CloseHandle(hvadkaA);
    CloseHandle(hvadkaB);

    ::GetSystemTime(&now);
    while(!primes.empty())
    {
        printf("%d \t", primes.top());
        primes.pop();
    }
    printf("\nGotov za %d milisec", abs(now.wMilliseconds - then.wMilliseconds));
    system("pause>nul");
    return 0;
}

3 个答案:

答案 0 :(得分:3)

猛烈终止线程是一个坏主意,因为它可能会使您的进程处于不良状态。如果您的线程在循环中运行,您可以在外部设置一些标志,线程可以测试并决定是否自行终止(这可以通过简单地退出thead函数来完成)。请记住用互斥锁保护你的旗帜。

您可能需要查看其他一些模式。您可能希望将您的素数范围放入队列中。然后,每个工作线程可以从队列中提取值并执行搜索。这样,您可以均匀地分割负载,而不会杀死并重新启动线程。

答案 1 :(得分:0)

你的寻找算法的效率非常低,但我现在想谈谈这个......

线程:

为每个线程设置一个作业队列..杀死/终止短生存线程,特别是对于寻找主要声音而言,这是一个非常糟糕的主意。

避免争用。不要对primes.push(i);使用锁定。为每个线程设置一个单独的队列,将结果推送到那里。当线程以范围结束时,然后进入criticalsection并合并结果。这样你很难锁定。

答案 2 :(得分:0)

我建议给每个线程一个块A(1-100)和B(101-200),为每个线程分配一个模数。例如,A将采用所有奇数索引,B将采用所有偶数索引,得到A {1,3,5,7,9,...,191,193,195,197,199}和B {2,4,6,8,..., 190,192,194,196,198,200}。这可能是负载平衡线程的最快捷,最简单的方法,因为计算的复杂性将是均匀分布的。

下一个建议是添加一个bool,表示是否可以继续处理。然后,在每次计算开始之前,检查是否可以继续。通过这种方式,您可以在不终止线程的情况下停止计算,但每次循环需要额外进行一次比较。

#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <queue>
#include <cmath>
#include <iostream>
using namespace std;
bool run;
priority_queue<int> primes;
CRITICAL_SECTION critical;
struct args
{
    int begin;
    int end;
}par1, par2;

int e_prosto(int n)
{
    for(int i = 2; i*i<(n + 1) ; i++)
    if (n & 1 == 0 || n % i == 0) return 0;
    return 1;
}

unsigned int __stdcall rabotnik(void* n)
{
    struct args *lPar = (args*) n;
    for(int i = lPar->begin; i < lPar->end && run; i++)
    {
        if(e_prosto(i)){
            EnterCriticalSection(&critical);
            primes.push(i);
            LeaveCriticalSection(&critical);
        }
    }
    run = false;
    return 0;
}

int main()
{
    int foo;
    printf(" Tarsene na prosti do: ");
    scanf("%d", &foo);
    par1.begin=1;
    par1.end=foo/2+1;
    par2.begin=foo/2+1;
    par2.end=foo;
    run = true;

    HANDLE hvadkaA, hvadkaB;
    InitializeCriticalSection(&critical);
    SYSTEMTIME st, now, then;

    hvadkaA = (HANDLE)_beginthreadex(0, 0, &rabotnik, (void*)&par1, 0, 0);
    hvadkaB = (HANDLE)_beginthreadex(0, 0, &rabotnik, (void*)&par2, 0, 0);
    ::GetSystemTime(&then);
    WaitForSingleObject(hvadkaA, INFINITE);
    WaitForSingleObject(hvadkaB, INFINITE);

    CloseHandle(hvadkaA);
    CloseHandle(hvadkaB);

    ::GetSystemTime(&now);
    while(!primes.empty())
    {
        printf("%d \t", primes.top());
        primes.pop();
    }
    printf("\nGotov za %d milisec", abs(now.wMilliseconds - then.wMilliseconds));
    system("pause>nul");
    return 0;
}

另一种方法是将范围划分为多个块,然后在线程完成时为其提供一个新块来处理。这样做的好处是不会在计算中增加额外的开销,但需要更多的代码(因此您需要重用线程并监听任何线程来完成,而不仅仅是一个)。为了具有任何价值,您可能需要更大的范围,您可能需要根据复杂程度改变块大小(块大小{1-100},{101-150},{151-175},{176-183}, {184-187},......)。使用代码的快速示例(具有对称块大小):

#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <queue>
#include <cmath>
#include <iostream>
using namespace std;
priority_queue<int> primes;
CRITICAL_SECTION critical;
typedef struct args
{
    int begin;
    int end;

    //Helper method for initalizing struct
    void setAll(int inBegin, bool inEnd)
    {
    }

} *PArgs;

int e_prosto(int n)
{
    for(int i = 2; i*i<(n + 1) ; i++)
    if (n & 1 == 0 || n % i == 0) return 0;
    return 1;
}

static DWORD WINAPI rabotnik(LPVOID lpParam)
{
    struct args *lPar = (args*) lpParam;
    for(int i = lPar->begin; i < lPar->end; i++)
    {
        if(e_prosto(i)){
            EnterCriticalSection(&critical);
            primes.push(i);
            LeaveCriticalSection(&critical);
        }
    }
    return 0;
}

int main()
{
    const int NUM_THREAD = 2;           //Use named constant incase you want to change later.
    DWORD returnedThreadID;
    DWORD threadID[NUM_THREAD];
    HANDLE threadHandle[NUM_THREAD];    //Holds the handels for the threads.
    int foo,                            //Range size.
        fooBlockSize,                   //Number of elements in a block.
        nextBlock;
    PArgs par[NUM_THREAD];

    printf(" Tarsene na prosti do: ");
    scanf("%d", &foo);                  //Get range size from user.
    fooBlockSize = foo / (NUM_THREAD * 10); //Set number of elements in a block.

    InitializeCriticalSection(&critical);
    SYSTEMTIME st, now, then;

    for (int i = 0; i < NUM_THREAD; i++) 
    {
        par[i] = (PArgs) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PArgs)); 
           // If the allocation fails, the system is out of memory so terminate execution.
        if( par[i] == NULL ){ cout<<"par HeapAlloc failed with error# "<<GetLastError()<<endl<<"Will now quit."<<endl; ExitProcess(2);}
    }

    for(int i = 0; i < NUM_THREAD; i++)
    {
        par[i]->begin = (fooBlockSize * i) + 1;
        par[i]->end = par[i]->begin + fooBlockSize;
        threadHandle[i] = CreateThread(NULL, 0, rabotnik, par[i], CREATE_SUSPENDED, &threadID[i]);
    }
    nextBlock = NUM_THREAD;

    ::GetSystemTime(&then);
    for (int i = 0; i < NUM_THREAD; i++)
    {
        ResumeThread(threadHandle[i]);      //Start threads
    }

    while( ((fooBlockSize * nextBlock) + 1) < foo)
    {
        returnedThreadID = WaitForMultipleObjects(NUM_THREAD, threadHandle, false, INFINITE);   //Wait for a thread to complete.
        for(int i = 0; i<NUM_THREAD;i++)
        {
            if(returnedThreadID = threadID[i])
            {
                //Update the thread's arguments with the new block.
                par[i]->begin = (fooBlockSize * nextBlock) + 1;
                par[i]->end = par[i]->begin + fooBlockSize;

                //Restart the thread.
                ResumeThread(threadHandle[i]);
                nextBlock++;
            }
        }
    }

    for (int i = 0; i < NUM_THREAD; i++)
    {
        //Return heap memorry (good practice, though Windows should return it all when the process terminates).
        if (HeapFree(GetProcessHeap(), 0, par[i]) == 0)
        {
            cout<<"HeapFree failed for par["<<i<<"]"<<endl;
        }

        //Not sure we need to close the threads, but it was in original version.
        CloseHandle(threadHandle[i]);
    }

    ::GetSystemTime(&now);
    while(!primes.empty())
    {
        printf("%d \t", primes.top());
        primes.pop();
    }
    printf("\nGotov za %d milisec", abs(now.wMilliseconds - then.wMilliseconds));
    system("pause>nul");
    return 0;
}

在增加块数与块大小之间存在折衷。在增加块的数量意味着只有一个块能够处理(线程[0]完成并且线程[1]完成时没有任何东西可以处理)所花费的时间更少,但也意味着将有我花了更多的时间等待调度程序循环来分配一个新的块来处理。有了你的问题陈述,我希望找到更高级别的素数需要很长时间才能无关紧要。

正如其他答案所述,不要使用相同的堆栈来存储每个线程找到的素数值(处理锁定所需的时间过长)。如果你想要以数字顺序返回素数,我建议重写打印素数的循环,使它同时通过两个堆栈,打印下一个值(按顺序)。有点像:

    while(!primes1.empty() && !primes2.empty())
    {
        if(primes1.top() > primes2.top())
        {
            printf("%d \t", primes1.top());
            primes1.pop();
        }
        else
        {
            printf("%d \t", primes2.top());
            primes2.pop();
        }
    }

一旦另一个堆栈为空,你将不得不处理一个堆栈中剩余的值(或者可能在每个堆栈的底部放置一个-1,因此如果堆栈为空,那么所有大于-1的值都已经打印)。

另一个解决方案是维护一个排序的素数列表,每次线程返回时都会更新。然后可以将其复制到par结构中以便更快地进行素数检测(如果一个数字可以被现有的素数均分,则该数字不是素数)。

注意:我没有对这些示例进行过测试,但它们应该足够接近,以便为您提供一般性的想法。