[PE]经典八炮 2022-02-04 16:52 采纳率: 76.9%
浏览 96
已结题

C++多线程有时会异常

这是一个五子棋人机对战游戏,其中AI的部分已经单独测试通过,这里只是用MFC实现了一下,加了个多线程。我的思路是,电脑思考时开一个多线程,然后立刻detach,防止阻塞了消息循环。搜索结束会给对话框发一个消息,然后继续游戏。代码非常长,这是一部分。GameTree类的所有代码测试通过,没有异常,这里就不给出代码了。问题是,有的时候,AI正在搜索,突然就弹出一个对话框,大概是abort() has been called,断点大致是在GameTree搜索那里,而且很难复现这个bug,大概十几局出现一次,出现的时候,点调用堆栈,就是在自定义线程内部运行的。如何解决这个问题呢?
如果需要远程协助或者完整代码可以si-xin我

// GoBangDlg.cpp: 实现文件
//

#include "pch.h"
#include "framework.h"
#include "GoBang.h"
#include "GoBangDlg.h"
#include "afxdialogex.h"
#include"resource.h"
#include <fstream>
#include<thread>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CGoBangDlg 对话框



CGoBangDlg::CGoBangDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_GOBANG_DIALOG, pParent),AI(nullptr)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CGoBangDlg::DoDataExchange(CDataExchange* pDX)
{
    IsPlaying = false;
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < SIZE; j++)
        {
            ChessBoard[i][j] = -1;
        }
    }//初始化棋盘
    CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CGoBangDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_START, &CGoBangDlg::OnBnClickedStart)
    ON_BN_CLICKED(IDC_QUIT, &CGoBangDlg::OnBnClickedQuit)
    ON_WM_LBUTTONUP()
    ON_WM_SETCURSOR()
    ON_WM_CLOSE()
    ON_BN_CLICKED(IDC_ENDGAME, &CGoBangDlg::OnBnClickedEndgame)
    ON_BN_CLICKED(IDC_REPENTANCE, &CGoBangDlg::OnBnClickedRepentance)
    ON_BN_CLICKED(IDC_SAVE, &CGoBangDlg::OnBnClickedSave)
    ON_BN_CLICKED(IDC_OPEN, &CGoBangDlg::OnBnClickedOpen)
    ON_MESSAGE(MM_AIPUTCHESS,&CGoBangDlg::OnAIPutChess)
END_MESSAGE_MAP()


// CGoBangDlg 消息处理程序

BOOL CGoBangDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码
    SetBackgroundImage(IDB_BACKGROUNDIMAGE);
    CString filename = AfxGetApp()->m_lpCmdLine;
    if (filename == L"")
        return TRUE;
    filename.Remove('\"');
    if (filename.Mid(filename.ReverseFind('.')) == ".gob")
        OpenFile(filename);
    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CGoBangDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {        
        CPaintDC dc(this);
        CPen pen(PS_SOLID, 2, RGB(0, 0, 0));
        dc.SelectObject(pen);
        for (int i = 0; i < SIZE; i++)
        {
            dc.MoveTo(50, 50 + i * 50);
            dc.LineTo(750, 50 + i * 50);
        }//绘制棋盘横线
        for (int i = 0; i < SIZE; i++)
        {
            dc.MoveTo(50 + i * 50, 50);
            dc.LineTo(50 + i * 50, 750);
        }//绘制棋盘竖线
        for (int nx = 0; nx < SIZE; nx++)
        {
            for (int ny = 0; ny < SIZE; ny++)
            {

                int color = GetChessBoardColor(nx, ny);
                if (color == 0)//白棋
                {
                    CBrush brush_w(RGB(255, 255, 255));
                    const CPoint o(50 * nx + 50, 50 * ny + 50);//圆心
                    dc.SelectObject(brush_w);
                    dc.Ellipse(o.x - 15, o.y - 15, o.x + 15, o.y + 15);
                }
                else if (color == 1)//黑棋
                {
                    CBrush brush_b(RGB(0, 0, 0));
                    const CPoint o(50 * nx + 50, 50 * ny + 50);//圆心
                    dc.SelectObject(brush_b);
                    dc.Ellipse(o.x - 15, o.y - 15, o.x + 15, o.y + 15);
                }
            }
        }
    }
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CGoBangDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}



void CGoBangDlg::OnBnClickedStart()
{
    if (IsPlaying)//重玩,需要delete掉AI,并且判断计算的线程是否在执行
    {
        if (IsAI && !NowColor)//AI正在计算,千万不能直接重玩,因为计算线程处于detach状态,对话框销毁后再发送消息会异常!
        {
            MessageBoxW(L"AI正在计算,重玩会产生异常,请等待AI计算完这一步!", L"五子棋", MB_OK | MB_ICONWARNING);
            return;
        }
        if (MessageBoxW(L"确定要重玩吗?", L"五子棋", MB_YESNO | MB_ICONQUESTION) == IDNO)
            return;
        delete AI;
    }
    if (MessageBoxW(L"是否使用人机对战?", L"五子棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
    {
        IsAI = true;
        GetDlgItem(IDC_ENDGAME)->EnableWindow(TRUE);
        GetDlgItem(IDC_SAVE)->EnableWindow(FALSE);
        AI = new GameTree(3);//往后推3步
    }
    else
    {
        IsAI = false;
        GetDlgItem(IDC_ENDGAME)->EnableWindow(TRUE);
        GetDlgItem(IDC_SAVE)->EnableWindow(TRUE);
    }
    GetDlgItem(IDC_START)->SetWindowTextW(L"重玩");
    IsPlaying = true;
    NowColor = 1;//黑先
    index = -1;
    GetDlgItem(IDC_REPENTANCE)->EnableWindow(FALSE);
    CleanChessBoard();
}


void CGoBangDlg::OnBnClickedQuit()
{
    if (IsPlaying && IsAI && !NowColor)//AI正在计算,千万不能直接退出,因为计算线程处于detach状态,对话框销毁后再发送消息会异常!
    {
        MessageBoxW(L"AI正在计算,退出会产生异常,请等待AI计算完这一步!", L"五子棋", MB_OK | MB_ICONWARNING);
        return;
    }
    if (!IsPlaying || MessageBoxW(L"正在游戏中,确定要退出吗?", L"五子棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
    {
        EndGame();
        EndDialog(0);
    }
}


void CGoBangDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
    if (!IsPlaying || point.x < 40 || point.x>760 || point.y < 40 || point.y>760 || (IsAI && !NowColor))
        return;
    int x = int(round(point.x / 50.0) - 1);
    int y = int(round(point.y / 50.0) - 1);
    //将鼠标坐标转为数组下标
    if (GetChessBoardColor(x, y) != -1)//如果已有棋子
        return;
    SetChessBoardColor(x, y, NowColor);
    NowColor = (!NowColor);
    SendMessage(WM_SETCURSOR);
    index++;
    order[index].x = x;
    order[index].y = y;
    if (IsAI)
    {
        /*
        * 创建线程计算,一面阻塞消息队列导致对话框无响应。计算完毕会发送消息MM_AIPUTCHESS,进而调用
        * OnAIPutChess函数来放置棋子等等。
        */
        std::thread searchThread(&CGoBangDlg::AISearch, this, x, y);
        searchThread.detach();
    }
    else
    {
        GetDlgItem(IDC_REPENTANCE)->EnableWindow(index > -1);
        //如果可以悔棋,取消禁用“悔棋”按钮,否则禁用“悔棋”按钮
        int winner = GetWinner();
        if (winner != -1 || index == (SIZE * SIZE - 1))
        {
            if (winner == 0)
                MessageBoxW(L"白棋胜利!", L"五子棋", MB_OK | MB_ICONINFORMATION);
            else if (winner == 1)
                MessageBoxW(L"黑棋胜利!", L"五子棋", MB_OK | MB_ICONINFORMATION);
            else
                MessageBoxW(L"平局!", L"五子棋", MB_OK | MB_ICONINFORMATION);
            EndGame();
        }
    }
}

int CGoBangDlg::GetChessBoardColor(int nx, int ny)
{
    return ChessBoard[ny][nx];
}

void CGoBangDlg::SetChessBoardColor(int nx, int ny, int color)
{
    ChessBoard[ny][nx] = color;
    CDC* dc = this->GetDC();
    CPen pen(PS_SOLID, 2, RGB(0, 0, 0));
    dc->SelectObject(pen);
    if (color == 0)//白棋
    {
        CBrush brush_w(RGB(255, 255, 255));
        const CPoint o(50 * nx + 50, 50 * ny + 50);//圆心
        dc->SelectObject(brush_w);
        dc->Ellipse(o.x - 15, o.y - 15, o.x + 15, o.y + 15);
    }
    else if (color == 1)//黑棋
    {
        CBrush brush_b(RGB(0, 0, 0));
        const CPoint o(50 * nx + 50, 50 * ny + 50);//圆心
        dc->SelectObject(brush_b);
        dc->Ellipse(o.x - 15, o.y - 15, o.x + 15, o.y + 15);
    }
    else//清除该坐标棋子,需要重绘,用于悔棋
    {
        Invalidate();
    }
}

void CGoBangDlg::EndGame()
{
    CleanChessBoard();
    IsPlaying = false;
    index = -1;
    GetDlgItem(IDC_START)->SetWindowTextW(L"开始游戏");
    GetDlgItem(IDC_ENDGAME)->EnableWindow(FALSE);
    GetDlgItem(IDC_REPENTANCE)->EnableWindow(FALSE);
    GetDlgItem(IDC_SAVE)->EnableWindow(FALSE);
    if (IsAI && AI)
    {
        delete AI;
        AI = nullptr;
    }
}

void CGoBangDlg::CleanChessBoard()
{
    for (int i = 0; i < SIZE; i++)
    {
        for (int j = 0; j < SIZE; j++)
        {
            ChessBoard[i][j] = -1;
        }
    }
    Invalidate();
}

void CGoBangDlg::OpenFile(CString filename)
{
    std::ifstream infile;
    infile.open(CStringA(filename));
    if (!infile)
    {
        MessageBoxW(L"打开失败!", L"五子棋", MB_OK | MB_ICONERROR);
        return;
    }
    for (int y = 0; y < 15; y++)
    {
        for (int x = 0; x < 15; x++)
        {
            int t;
            infile >> t;
            infile.seekg(infile.tellg().operator+(1));
            ChessBoard[y][x] = t;
            /*
            因为保存文件已经用了GetChessBoardColor函数,
            文件中的数字是正常顺序,不能使用SetChessBoardColor函数。
            而且,SetChessBoardColor遇到-1就会刷新棋盘,性能不好。
            */
        }
    }
    Invalidate();//绘制棋盘和棋子
    infile >> NowColor;
    infile.seekg(infile.tellg().operator+(1));
    infile >> index;
    for (int i = 0; i <= index; i++)
    {
        infile.seekg(infile.tellg().operator+(1));
        infile >> order[i].x;
        infile.seekg(infile.tellg().operator+(1));
        infile >> order[i].y;
    }
    infile.close();
    GetDlgItem(IDC_START)->SetWindowTextW(L"重玩");
    GetDlgItem(IDC_ENDGAME)->EnableWindow(TRUE);
    GetDlgItem(IDC_REPENTANCE)->EnableWindow(index > 0);
    GetDlgItem(IDC_SAVE)->EnableWindow(TRUE);
    IsPlaying = true;
}

int CGoBangDlg::GetChessCount(int nx, int ny)
{
    int color = GetChessBoardColor(nx, ny);
    if (color == -1)
        return -1;

    int x = nx, y = ny;
    int m_max, count;
    while (--y >= 0 && GetChessBoardColor(x, y) == color);
    y++;
    for (count = 1; (++y < SIZE) && (GetChessBoardColor(x, y) == color); count++);
    m_max = count;
    //y轴
    x = nx, y = ny;
    while (--x >= 0 && GetChessBoardColor(x, y) == color);
    x++;
    for (count = 1; ++x < SIZE && GetChessBoardColor(x, y) == color; count++);
    if (m_max < count)
        m_max = count;
    //x轴
    x = nx, y = ny;
    while (x - 1 >= 0 && y - 1 >= 0 && GetChessBoardColor(x - 1, y - 1) == color)
        x--, y--;
    for (count = 1; x + 1 < SIZE && y + 1 < SIZE && GetChessBoardColor(x + 1, y + 1) == color; count++)
        x++, y++;
    if (m_max < count)
        m_max = count;
    //左下到右上
    x = nx, y = ny;
    while (x - 1 >= 0 && y + 1 < SIZE && GetChessBoardColor(x - 1, y + 1) == color)
        x--, y++;
    for (count = 1; x + 1 < SIZE && y - 1 >= 0 && GetChessBoardColor(x + 1, y - 1) == color; count++)
        x++, y--;
    if (m_max < count)
        m_max = count;
    //左上到右下
    return m_max;
}

int CGoBangDlg::GetWinner()
{
    if (GetChessCount(order[index].x, order[index].y) >= 5)
        return NowColor;
    return -1;
}

void CGoBangDlg::AISearch(uint8_t x, uint8_t y)
{
    auto&& pos = AI->AIGetNextPos(x, y);
    SendMessageW(MM_AIPUTCHESS, pos.first, pos.second);
}


BOOL CGoBangDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    POINT point;
    GetCursorPos(&point);
    ScreenToClient(&point);
    if (!IsPlaying || point.x < 40 || point.x>760 || point.y < 40 || point.y>760)
        return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
    if (NowColor == 1)//黑棋
        SetCursor(LoadCursorW(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_BLACK)));
    else
        SetCursor(LoadCursorW(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_WHITE)));
    return TRUE;
}


void CGoBangDlg::OnClose()
{
    if (IsPlaying && IsAI && !NowColor)//AI正在计算,千万不能直接退出,因为计算线程处于detach状态,对话框销毁后再发送消息会异常!
    {
        MessageBoxW(L"AI正在计算,退出会产生异常,请等待AI计算完这一步!", L"五子棋", MB_OK | MB_ICONWARNING);
        return;
    }
    if (!IsPlaying || MessageBoxW(L"正在游戏中,确定要退出吗?", L"五子棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
    {
        EndGame();
        CDialogEx::OnClose();
    }
}


void CGoBangDlg::OnBnClickedEndgame()
{
    if (IsAI && !NowColor)//AI正在计算,千万不能直接退出,因为计算线程处于detach状态,对话框销毁后再发送消息会异常!
    {
        MessageBoxW(L"AI正在计算,退出会产生异常,请等待AI计算完这一步!", L"五子棋", MB_OK | MB_ICONWARNING);
        return;
    }
    if (MessageBoxW(L"确定要结束本局吗?", L"五子棋", MB_YESNO | MB_ICONQUESTION) == IDYES)
        EndGame();
}


void CGoBangDlg::OnBnClickedRepentance()
{
    SetChessBoardColor(order[index].x, order[index].y, -1);//清除上一步棋
    index--;
    GetDlgItem(IDC_REPENTANCE)->EnableWindow(index > -1);
    NowColor = (!NowColor);//改变棋子颜色
}


void CGoBangDlg::OnBnClickedSave()
{
    CFileDialog filedlg(FALSE);
    filedlg.m_ofn.lpstrFilter = L"五子棋文件(*.gob)\0*.gob\0\0";
    if (filedlg.DoModal() != IDOK)
        return;
    CString filename = filedlg.GetPathName();
    if (filedlg.GetFileExt() == L"")
        filename += ".gob";
    std::ofstream outfile;
    outfile.open(CStringA(filename));
    if(!outfile)
    {
        MessageBoxW(L"保存失败!", L"五子棋", MB_OK | MB_ICONERROR);
        return;
    }
    for (int y = 0; y < 15; y++)
    {
        for (int x = 0; x < 15; x++)
        {
            outfile << GetChessBoardColor(x, y) << '\0';
        }
        outfile << '\r';
    }
    //输出ChessBoard数组
    outfile <<NowColor<<'\r'<< index << '\r';
    for (int i = 0; i <= index; i++)
        outfile << order[i].x << '\0' << order[i].y << '\r';
    //输出order数组
    outfile.close();
    MessageBoxW(L"保存成功!", L"五子棋", MB_OK | MB_ICONINFORMATION);
}
/*
    五子棋文件格式:
    1.扩展名:gob。
    2.文件由多个数字组成,保存了程序运行时的所有变量。
    3.第1到第SIZE*SIZE(225)个数字:记录棋盘上每个交叉点的状态。(ChessBoard)
    4.第SIZE*SIZE+1(226)个数字:记录下一步是哪一方走。(NowColor)
    5.第SIZE*SIZE+2(227)个数字:记录已经走了多少步棋,供悔棋功能用。(index)
    6.第SIZE*SIZE+3(228)到第(SIZE*SIZE+3+第226个数字*2)个数字:
    每两个数为一组,记录每一步棋的x坐标和y坐标。(order)
*/

void CGoBangDlg::OnBnClickedOpen()
{
    if (IsPlaying)
    {
        if (IsAI && !NowColor)//AI正在计算,千万不能直接重玩,因为计算线程处于detach状态,对话框销毁后再发送消息会异常!
        {
            MessageBoxW(L"AI正在计算,重玩会产生异常,请等待AI计算完这一步!", L"五子棋", MB_OK | MB_ICONWARNING);
            return;
        }
        if (MessageBoxW(L"正在游戏中,本局将被结束。确定要打开棋局吗?", L"五子棋", MB_YESNO | MB_ICONQUESTION) == IDNO)
            return;
        delete AI;
    }
    CFileDialog filedlg(TRUE);
    filedlg.m_ofn.lpstrFilter = L"五子棋文件(*.gob)\0*.gob\0\0";
    if (filedlg.DoModal() != IDOK)
        return;
    CString filename = filedlg.GetPathName();
    if (filedlg.GetFileExt() == L"")
        filename += ".gob";
    OpenFile(filename);
}

LRESULT CGoBangDlg::OnAIPutChess(WPARAM x, LPARAM y)
{
    SetChessBoardColor(x, y, NowColor);
    NowColor = (!NowColor);
    SendMessageW(WM_SETCURSOR);
    auto&& winner = AI->GetWinner();
    if (winner == GameTree::Node::BLACK)
        MessageBoxW(L"玩家(黑棋)胜利!", L"五子棋", MB_OK | MB_ICONINFORMATION);
    else if (winner == GameTree::Node::WHITE)
        MessageBoxW(L"电脑(白棋)胜利!", L"五子棋", MB_OK | MB_ICONINFORMATION);
    else if (index == (SIZE * SIZE - 1))
        MessageBoxW(L"平局!", L"五子棋", MB_OK | MB_ICONINFORMATION);
    else
        return 0;
    EndGame();
    return 0;
}
  • 写回答

2条回答 默认 最新

  • [PE]经典八炮 2022-02-07 11:46
    关注

    问题解决了,就是内存不足的问题,原来还是因为算法问题,以前枚举的时候没加后面的类型说明符uint8_t,竟然默认是uint32_t,4个字节,现在是1个字节,可以节省四分之三的内存,看了下任务管理器,修改之前的内存最大达到了1.2G左右,然后就崩溃了,现在最多也就500M左右,完全没有问题了。以前没发现这个问题,可能是现在测试的时候我开着其他很多程序,占用了一部分内存,而以前测试的时候只开着一个程序,电脑内存勉强够用。

    img

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论 编辑记录
查看更多回答(1条)

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 2月7日
  • 已采纳回答 2月7日
  • 修改了问题 2月4日
  • 修改了问题 2月4日
  • 展开全部

悬赏问题

  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
  • ¥15 ubuntu子系统密码忘记
  • ¥15 保护模式-系统加载-段寄存器