Пример pipe () и fork (): basic_string :: M_construct null недопустимо - PullRequest
0 голосов
/ 11 апреля 2020

Еще одна попытка заставить параллельные процессы работать. Пожалуйста, извините количество кода, но каждая попытка сократить его приводит к ошибке vani sh.

То, что я тестировал до сих пор:

  • отправка int от родителя к потомку, от потомка к родителю и от родителя к потомку, а затем обратно: works
  • обработка списка int: отправка от родителя к потомку, изменение и возврат к родителю: works
  • дополнительные данные: int + string от родителя к потомку измените и вернитесь к родителю: работает
  • список данных таким же образом: работает

Но когда я запускаю ту же функцию, которая работает во второй раз, это всегда терпит неудачу.

Это функция, которая создает дочерний процесс:

//parent sends binary data from list to child which sends back modified data
bool processParallel6(std::vector<std::pair<int, std::string>> & data)
{
    //define pipe
    int parent2Child[2];
    int child2Parent[2];

    //create pipe
    pipe(parent2Child);
    pipe(child2Parent);

    //fork
    pid_t child = fork();

    if(child == 0) //child process
    {
        //close not needed end of pipe
        close(parent2Child[1]);
        close(child2Parent[0]);

        for(;;)
        {
            struct pollfd pfd;
            pfd.fd = parent2Child[0];
            pfd.events = POLLIN;

            //wait until data is available at the pipe
            cout << "c: poll ..." << endl;
            if(poll(&pfd, 1, -1) < 0)
            {
                cout << "c: poll: " << strerror(errno) << endl;
                exit(-1);
            }
            cout << "c: poll says there are data" << endl;

            if((pfd.revents&POLLIN) == POLLIN)
            {
                int data;
                std::string text;
                if(!readData3(parent2Child[0], data, text))
                    exit(-2);
                cout << "c: data received: " << data << " " << text <<  endl;

                if(data == -1)
                    break;

                if(!writeData3(child2Parent[1], data * 2, text + text))
                    exit(-3);
                cout << "c: sent data to parent: " << 2 * data << " " << text + text << endl;
            }
        }

        close(parent2Child[0]);
        close(child2Parent[1]);
        exit(0);
    }
    else //parent process
    {
        //close not needed end of pipe
        close(parent2Child[0]);
        close(child2Parent[1]);

        //send data to child
        if(!writeData3(parent2Child[1], data.back().first, data.back().second))
            return false;
        cout << "p: wrote data: " << data.back().first << " " << data.back().second << endl;
        data.pop_back();

        //read result from child
        for(;;)
        {
            struct pollfd pfd;
            pfd.fd = child2Parent[0];
            pfd.events = POLLIN;

            //wait until data is available at the pipe
            cout << "p: poll ..." << endl;
            if(poll(&pfd, 1, -1) < 0)
            {
                cout << "p poll: " << strerror(errno) << endl;
                return false;
            }
            cout << "p: poll says there are data" << endl;

            if((pfd.revents&POLLIN) == POLLIN)
            {
                int data;
                std::string text;
                if(!readData3(child2Parent[0], data, text))
                    return false;
                cout << "p: data received: " << data << " " << text << endl;
            }

            if(data.empty())
                break;

            if(!writeData3(parent2Child[1], data.back().first, data.back().second))
                return false;
            cout << "p: wrote data: " << data.back().first << " " << data.back().second << endl;
            data.pop_back();
        }

        //send stop data
        if(!writeData3(parent2Child[1], -1, "notext"))
            return false;
        cout << "p: sent stop data " << endl;

        //wait for child to end
        wait(nullptr);

        //close all pipes
        close(parent2Child[1]);
        close(child2Parent[0]);
    }

    return true;
}

Для чтения и записи данных я использую следующие две функции:

bool readData3(int fd, int & number, std::string & text)
{
    char numberBuf[sizeof(int)];
    int bytesRead = read(fd, numberBuf, sizeof(int));
    if(bytesRead > 0)
    {
        number = *(int *)numberBuf;
    }
    else if(bytesRead < 0)
    {
        cout << "readData3: " << strerror(errno) << endl;
        return false;
    }

    char sizeBuf[sizeof(int)];
    int size = -1;
    bytesRead = read(fd, sizeBuf, sizeof(int));
    if(bytesRead > 0)
    {
        size = *(int *)sizeBuf;
    }
    else if(bytesRead < 0)
    {
        cout << "readData3: " << strerror(errno) << endl;
        return false;
    }

    char textBuf[size];
    bytesRead = read(fd, textBuf, size);
    if(bytesRead > 0)
    {
        text = std::string(textBuf);
    }
    else if(bytesRead < 0)
    {
        cout << "readData3: " << strerror(errno) << endl;
        return false;
    }
    return true;
}

bool writeData3(int fd, const int number, const std::string text)
{
    int bytesWritten = write(fd, &number, sizeof(int));
    if(bytesWritten < 0)
    {
        cout << "writeData3: " << strerror(errno) << endl;
        return false;
    }

    int size = text.size() + 1;
    bytesWritten = write(fd, &size, sizeof(int));
    if(bytesWritten < 0)
    {
        cout << "writeData3: " << strerror(errno) << endl;
        return false;
    }

    bytesWritten = write(fd, text.c_str(), size);
    if(bytesWritten < 0)
    {
        cout << "writeData3: " << strerror(errno) << endl;
        return false;
    }

    return true;
}

Наконец, я запустите его так:

#include <iostream>
#include <vector>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <bitset>
#include <memory>
#include <poll.h>
#include <cstring>

using namespace std;

int main(int /*argc*/, char* /*argv*/[])
{
    std::vector<std::pair<int, std::string>> data;
    data.push_back(std::make_pair(1, "one"));
    data.push_back(std::make_pair(2, "two"));
    cout << "6a ########################################################" << endl << flush;
    processParallel6(data);
    cout << "6b ########################################################" << endl << flush;
    processParallel6(data);

    return 0;
}

Это вывод:

6a ###############################################
p: wrote data: 2 two
p: poll ...
c: poll ...
c: poll says there are data
c: data received: 2 two
p: poll says there are data
p: data received: 4 twotwo
p: wrote data: 1 one
p: poll ...
c: sent data to parent: 4 twotwo
c: poll ...
c: poll says there are data
c: data received: 1 one
p: poll says there are data
p: data received: 2 oneone
p: sent stop data 
c: sent data to parent: 2 oneone
c: poll ...
c: poll says there are data
c: data received: -1 notext
6b ###################################################
c: poll ...
terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_M_construct null not valid
c: poll says there are data
c: poll ...
c: poll says there are data
c: poll ...

Последние 4 строки повторяются тысячи раз. Этот вывод появляется в большинстве случаев, но иногда я видел ошибку std :: bad_allo c. Когда я пытаюсь использовать strace, он тоже дает сбой, но когда он запускается, я сразу после второго запуска processParallel6 () вижу строку с mmap, ENOEM и 'Cannot allocate memory'

Что здесь происходит? Почему это работает в первый раз, а не во второй раз?

1 Ответ

1 голос
/ 12 апреля 2020

Вы пытаетесь скопировать недопустимую ссылку std::string.


std::terminate вызывается в конструкторе std::string. Конструктор неявно вызывается в processParallel6 при вызове writeData3:

bool writeData3(int fd, const int number, const std::string text)

...

//send data to child
if(!writeData3(parent2Child[1], data.back().first, data.back().second))
    return false;

Вы ожидаете, что data.back().second является действительной ссылкой string, но ничто в коде не гарантирует, что это так.

Вы создаете data и помещаете в него две записи:

data.push_back(std::make_pair(1, "one"));
data.push_back(std::make_pair(2, "two"));

При первом вызове processParallel6 вы запускаете следующий блок кода дважды :

if(!writeData3(parent2Child[1], data.back().first, data.back().second))
    return false;
cout << "p: wrote data: " << data.back().first << " " << data.back().second << endl;
data.pop_back();

На данный момент data пусто. Вы не можете сделать еще один вызов processParallel6, поскольку он ожидает, что data содержит хотя бы один элемент.

...