c — проблема с памятью при многопоточности

#c #multithreading #ncurses

#c #многопоточность #ncurses

Вопрос:

В настоящее время я изучаю потоки на c в университете, и у меня есть этот небольшой проект, включающий ncurses — прыгающие шары. Я хочу, чтобы шарики появлялись до тех пор, пока я не нажму «x». После того, как я нажимаю кнопку, он завершает работу, но он также показывает что-то о нарушении защиты памяти.

Когда я использую gdb, после нажатия «x» появляется надпись:

Поток 1 «p» принял сигнал SIGSEGV, ошибка сегментации. 0x00007ffff6e6b3c1 в _int_malloc (av=av@entry=0x7ffff71c2c40 , bytes=байты@entry=28) в malloc.c:3612

Проблема может быть в цикле for, но я не уверен.

Вот код, который я написал:

 #include "window.h"
#include <stdlib.h>
#include <time.h>
#include <thread>
#include <unistd.h>
#include <ncurses.h>
#include <atomic>

Window *window;
std::atomic<bool> run(true);


void exit() {
    while(run) {
        char z = getch();
        if(z == 'q') run = false;
    }
}

void ballFunction(int a) {
    int nr = a;
    while (run amp;amp; window->balls[nr]->counter < 5) {
        usleep(50000);
        window->balls[nr]->updateBall();
    }
    window->balls[nr]->x = -1;
    window->balls[nr]->y = -1;
} 

void updateWindow2() {
    while(run) {
        usleep(50000);
        window->updateWindow();
    }
    delete window;
}

int main() {
    srand(time(NULL));

    window = new Window();

    int i = 0;

    std::vector<std::thread> threads;

    std::thread threadWindow(updateWindow2);
    std::thread threadExit(exit);

    while(run = true) {
         window->addBall();
         threads.push_back(std::thread(ballFunction, i));
         i  ;
         sleep(1);
    }

    threadWindow.join();
    threadExit.join();

    for(int j=2; j<i 2; j  ) {
        threads[j].join();
    }

    return 0;
    }
  

 #include "window.h"
#include <ncurses.h>
#include <unistd.h>

Window::Window()
{
    initWindow();
}

Window::~Window()
{
    endwin(); 
}

void Window::initWindow()
{
    initscr();
    noecho();
    curs_set(FALSE); 
    clear();
    refresh();
}

void Window::addBall()
{
    Ball *ball = new Ball(this->ballCounter  );
    this->balls.push_back(ball);
}

void Window::updateWindow()
{
    for(int i = 0; i<ballCounter; i  )
    {
        if(balls[i]->update())
        {
            clear(balls[i]->yy, 
                balls[i]->xx);
            drawBall(balls[i]->y,
                 balls[i]->x);
        }
    }
    refresh();
}

void Window::clear(int y, int x)
{
    mvprintw(y, x, " ");
}

void Window::drawBall(int y, int x)
{
    mvprintw(y, x, "o"); 
}
  

Комментарии:

1. Большой сюрприз: можно отлаживать потоки и видеть их стек вызовов в большинстве разумных C IDE. Это возможно даже с помощью инструментов отладки более низкого уровня, таких как GDB.

2. Как выглядит Window класс? Вызов addBall в одном потоке при одновременном доступе balls[nr] к другим может привести к сбоям.

3. Не ваша ошибка, но void updateWindow2() это нетрадиционное место для delete window; нее вызовет меньше удивления, если вы переместите ее в конец main , чтобы она лучше сочеталась с window = new Window(); .

4. Добавить к комментарию @user4581301. Вероятно, использовать new и delete вручную в вашем коде — плохая идея. Вам следует взглянуть на средства динамического управления памятью стандартов c . Они гарантированно будут потокобезопасными .

5. Как и просил @1201ProgramAlarm, я добавил код класса Window.

Ответ №1:

Проблема в том, что у вас есть один поток, добавляющий к balls вектору, в то время как другие потоки читают из него.

При addBall вызовах push_back , если емкость вектора недостаточно велика, будет выделена новая память, существующие объекты перемещены или скопированы в новый блок, а старая память освобождена. Когда это происходит, запущенные потоки ballFunction могут выполнять запись в память, которая больше не выделена. Это приводит к неопределенному поведению, включая ошибку сегментации, с которой вы сталкиваетесь.

Вам нужно использовать некоторую форму мьютекса или убедиться, что balls вектору выделено достаточно места (с помощью balls.reserve(more_elements_than_youll_ever_create) ).

Кстати, ваш основной while цикл никогда не завершится, потому что у вас есть присваивание, а не сравнение в качестве условия (используйте while (run) или while (run == true) ).

Комментарии:

1. Большое спасибо! Это объясняет некоторые вещи — я действительно ценю это. Последний вопрос: предлагаете ли вы использовать balls.reserve в какой-либо конкретной части программы?