使用线程时出现意外输出

时间:2015-01-04 05:25:55

标签: c++ multithreading command-line-interface

我正在为游戏开发一个概念验证测试程序,其中某些操作是线程化的,并且信息输出到每个线程的命令窗口。到目前为止,我已经得到了基本的线程处理工作,但似乎我的被调用函数中的couting不是为每个线程编写的,而是每个线程都覆盖其他线程。

期望或预期的输出是每个线程将输出在mLaser的mCycle函数内输入的信息。从本质上讲,这是一个对每个对象进行排序的计时器,用于计算该对象完成其任务之前的时间。每个线程都应该有一个输出,所以如果有五个线程在运行,那么应该有五个计数器独立倒计时。

当前输出是这样的,每个线程在相同的空间中输出自己的信息,然后覆盖另一个线程试图输出的内容。

以下是该程序当前输出的示例:

  

直到周期的时间直到第74周期结束的时间:36完成:

     

92秒2秒按任意键继续。 。

如果您检查信息是如何从mCycle中获得的,那么您可以看到数字和其他文本位于不应该存在的位置的偏差。

这些线应该显示的更长:

  

第1周期结束的时间:

     

92秒

     

第2周期结束的时间:

     

112秒

     

第3周期结束的时间:

     

34秒

     

第4周期已经完成!

我不确定这是由于某种线程锁定是由于我的代码是如何构造的,还是仅仅是对我的输出编码的疏忽。如果我能看到一双新鲜的眼睛来查看代码并指出任何可能是错误的东西我会很感激。

这是我的代码,它应该可以在任何MSVS 2013安装中编译(不使用自定义库)

#include <iostream>
#include <Windows.h>
#include <string>
#include <vector>
#include <random>
#include <thread>
#include <future>

using namespace std;

class mLaser
{
public:
    mLaser(int clen, float mamt)
    {
        mlCLen = clen;
        mlMAmt = mamt;
    }

    int getCLen()
    {
        return mlCLen;
    }

    float getMAmt()
    {
        return mlMAmt;
    }

    void mCycle(int i1, int mCLength)
    {
        bool bMCycle = true;

        int mCTime_left = mCLength * 1000;
        int mCTime_start = GetTickCount(); //Get cycle start time
        int mCTime_old = ((mCTime_start + 500) / 1000);

        cout << "Time until cycle " << i1 << " is complete: " << endl;

        while (bMCycle)
        {
            cout << ((mCTime_left + 500) / 1000) << " seconds";

            bool bNChange = true;

            while (bNChange)
            {
                //cout << ".";

                int mCTime_new = GetTickCount();

                if (mCTime_old != ((mCTime_new + 500) / 1000))
                {
                    //cout << mCTime_old << " " << ((mCTime_new+500)/1000) << endl;
                    mCTime_old = ((mCTime_new + 500) / 1000);
                    mCTime_left -= 1000;
                    bNChange = false;
                }
            }
            cout << " \r" << flush;
            if (mCTime_left == 0)
            {
                bMCycle = false;
            }
        }

        cout << "Mining Cycle " << i1 << " finished" << endl;
        system("Pause");

        return true;
    }


    private:
    int mlCLen;
    float mlMAmt;
};

string sMCycle(mLaser ml, int i1, thread& thread);

int main()
{
    vector<mLaser> mlasers;
    vector<thread> mthreads;
    future<string> futr;

    random_device rd;
    mt19937 gen(rd());

    uniform_int_distribution<> laser(1, 3);
    uniform_int_distribution<> cLRand(30, 90);
    uniform_real_distribution<float> mARand(34.0f, 154.3f);

    int lasers;
    int cycle_time;
    float mining_amount;

    lasers = laser(gen);

    for (int i = 0; i < lasers-1; i++)
    {    
        mlasers.push_back(mLaser(cLRand(gen), mARand(gen)));
        mthreads.push_back(thread());
    }

    for (int i = 0; i < mlasers.size(); i++)
    {
        futr = async(launch::async, [mlasers, i, &mthreads]{return sMCycle(mlasers.at(i), i + 1, mthreads.at(i)); });

        //mthreads.at(i) = thread(bind(&mLaser::mCycle, ref(mlasers.at(i)), mlasers.at(i).getCLen(), mlasers.at(i).getMAmt()));
    }

    for (int i = 0; i < mthreads.size(); i++)
    {
        //mthreads.at(i).join();
    }


    //string temp = futr.get();
    //float out = strtof(temp.c_str(),NULL);

    //cout << out << endl; 

    system("Pause");
    return 0;
}

string sMCycle(mLaser ml, int i1, thread& t1)
{
    t1 = thread(bind(&mLaser::mCycle, ref(ml), ml.getCLen(), ml.getMAmt()));
    //t1.join();

    return "122.0";
}

2 个答案:

答案 0 :(得分:3)

虽然从多个线程同时写入std::cout必须是数据争用的,但不能保证并发写入不会交错。我不确定一个线程的一个写操作是否可以与来自另一个线程的一个写操作交错,但它们肯定可以在写操作之间交错(我认为来自不同线程的各个输出可以交错)。

关于标准流对象的并发访问(即std::coutstd::cin等),标准的含义见27.4.1 [iostream.objects.overview]第4段: / p>

  

同步访问同步(27.5.3.4)标准iostream对象的格式化和未格式化输入(27.7.2.1)和输出(27.7.3.1)函数或多个线程的标准C流不应导致数据竞争(1.10 )。 [注意:如果用户希望避免交错字符,则仍必须通过多个线程同步这些对象和流的并发使用。 - 后注]

如果您希望输出以某种形式显示,则需要同步对std::cout的访问权限,例如,使用互斥锁。

答案 1 :(得分:0)

虽然Dietmar的答案已经足够,但我决定采用另一种更为简单的方法。由于我正在创建类的实例,并且我在线程中访问这些实例,因此我选择在线程期间更新这些类的数据,然后在线程执行完毕后调用更新的数据。

这样我就不必处理像数据竞争这样烦人的问题,也不必在shared_future的向量中从异步中获取输出。这是我修改过的代码,以防其他人想要实现类似的东西:

#include <iostream>
#include <Windows.h>
#include <string>
#include <vector>
#include <random>
#include <thread>
#include <future>

using namespace std; //Tacky, but good enough fo a poc D:

class mLaser
{
public:
    mLaser(int clen, float mamt, int time_left)
    {
        mlCLen = clen;
        mlMAmt = mamt;
        mCTime_left = time_left;
        bIsCompleted = false;
    }

    int getCLen()
    {
        return mlCLen;
    }

    float getMAmt()
    {
        return mlMAmt;
    }

    void setMCOld(int old)
    {
        mCTime_old = old;
    }

    void mCycle()
    {
        if (!bIsCompleted)
        {
            int mCTime_new = GetTickCount(); //Get current tick count for comparison to mCOld_time

            if (mCTime_old != ((mCTime_new + 500) / 1000)) //Do calculations to see if time has passed since mCTime_old was set
            {
                //If it has then update mCTime_old and remove one second from mCTime_left.
                mCTime_old = ((mCTime_new + 500) / 1000);
                mCTime_left -= 1000;
            }

            cur_time = mCTime_left;
        }

        else
        {
            mCTime_left = 0;
        }
    }

    int getCTime()
    {
        return cur_time;
    }

    int getCTLeft()
    {
        return mCTime_left;
    }

    void mCComp()
    {
        bIsCompleted = true;
    }

    bool getCompleted()
    {
        return bIsCompleted;
    }

private:
    int mlCLen; //Time of a complete mining cycle
    float mlMAmt; //Amoung of ore produced by one mining cycle (not used yet)
    int cur_time; //The current time remaining in the current mining cycle; will be removing this as it is just a copy of mCTime_left that I was going to use for another possiblity to make this code work
    int mCTime_left; //The current time remaining in the current mining cycle
    int mCTime_old; //The last time that mCycle was called

    bool bIsCompleted; //Flag to check if a mining cycle has already been accounted for as completed
};

void sMCycle(mLaser& ml, int i1, thread& _thread); //Start a mining cycle thread

//Some global defines
random_device rd;
mt19937 gen(rd());

uniform_int_distribution<> laser(1, 10); //A random range for the number of mlaser entities to use
uniform_int_distribution<> cLRand(30, 90); //A random time range in seconds of mining cycle lengths
uniform_real_distribution<float> mARand(34.0f, 154.3f); //A random float range of the amount of ore produced by one mining cycle (not used yet)

int main()
{
    //Init some variables for later use
    vector<mLaser> mlasers; //Vector to hold mlaser objects
    vector<thread> mthreads; //Vector to hold threads
    vector<shared_future<int>> futr; //Vector to hold shared_futures (not used yet, might not be used if I can get the code working like this)

    int lasers; //Number of lasers to create
    int cycle_time; //Mining cycle time
    int active_miners = 0; //Number of active mining cycle threads (one for each laser)
    float mining_amount; //Amount of ore produced by one mining cycle (not used yet)

    lasers = laser(gen); //Get a random number
    active_miners = lasers; //Set this to that random number for the while loop later on

    //Create the mlaser objects and push them into the mlasers vector
    for (int i = 0; i < lasers; i++)
    {
        int clength = cLRand(gen);

        mlasers.push_back(mLaser(clength, mARand(gen), (clength * 1000)));

        //Also push thread obects into mthreads for each laser object
        mthreads.push_back(thread());
    }

    //Setup data for mining cycles
    for (int i = 0; i < mlasers.size(); i++)
    {
        int mCTime_start = GetTickCount(); //Get cycle start time
        mlasers.at(i).setMCOld(((mCTime_start + 500) / 1000));
    }

    //Print initial display for mining cycles
    for (int i = 0; i < mlasers.size(); i++)
    {
        cout << "Mining Laser " << i + 1 << " cycle will complete in " << (mlasers.at(i).getCTLeft() + 500) / 1000 << " seconds..." << endl;
    }

    while (active_miners > 0)
    {   
        for (int i = 0; i < mlasers.size(); i++)
        {
            //futr.push_back(async(launch::async, [mlasers, i, &mthreads]{return sMCycle(mlasers.at(i), i + 1, mthreads.at(i)); }));
            async(launch::async, [&mlasers, i, &mthreads]{return sMCycle(mlasers.at(i), i + 1, mthreads.at(i)); }); //Launch a thread for the current mlaser object
            //mthreads.at(i) = thread(bind(&mLaser::mCycle, ref(mlasers.at(i)), mlasers.at(i).getCLen(), mlasers.at(i).getMAmt()));
        }

        //Output information from loops
        //cout << " \r" << flush; //Return cursor to start of line and flush the buffer for the next info

        system("CLS");

        for (int i = 0; i < mlasers.size(); i++)
        {
            if (mlasers.at(i).getCTLeft() != 0) //If mining cycle is not completed
            {
                cout << "Mining Laser " << i + 1 << " cycle will complete in " << (mlasers.at(i).getCTLeft() + 500) / 1000 << " seconds..." << endl;
            }

            else if (mlasers.at(i).getCTLeft() == 0) //If it is completed
            {
                if (!mlasers.at(i).getCompleted())
                {
                    mlasers.at(i).mCComp();
                    active_miners -= 1;
                }

                cout << "Mining Laser " << i + 1 << " has completed its mining cycle!" << endl;
            }
        }
    }


    /*for (int i = 0; i < mthreads.size(); i++)
    {
        mthreads.at(i).join();
    }*/


    //string temp = futr.get();
    //float out = strtof(temp.c_str(),NULL);

    //cout << out << endl;

    system("Pause");
    return 0;
}

void sMCycle(mLaser& ml, int i1,thread& _thread)
{
    //Start thread
    _thread = thread(bind(&mLaser::mCycle, ref(ml)));

    //Join the thread
    _thread.join();
}