2016-03-17 2 views
2

Я новичок в многопоточном программировании, поэтому этот вопрос может показаться немного глупым, но мне действительно нужно разобраться в этом, поэтому я могу применить его к моему проекту (что намного сложнее). Следуйте моему коду, я пытаюсь иметь 2 потока (родительский и дочерний) для обновления одного и того же общего таймера при их выполнении и остановки, когда таймер достигает определенного предела. Но когда я компилирую и выполняю это, следуйте части кода, есть два разных результата: 1. дочерние отпечатки «сделано дочерним по 200000», но программа не выходит; 2. после того, как дочерние отпечатки «сделанные дочерним по 200000» и завершены, родитель продолжает выполнение, печатает несколько десятков строк «родительская работа» и «родительский элемент в 190000», затем печатает «сделано родителем по 200000» и программа выходы должным образом. Поведение, которое я хочу, относится к тем потокам, которые обновляют таймер, достигают предела и выходят, а другой поток должен прекратить выполнение и выйти. Я думаю, что, возможно, мне не хватает чего-то тривиального, но я попытался изменить код по-разному, и ничто из того, что я пытался, похоже, работает. Любая помощь будет оценена :)C++ многопоточное программирование с таймером

#include <iostream> 
#include <unistd.h> 
#include <mutex> 
#include <time.h> 

using namespace std; 

mutex mtx; 

int main() { 
    int rc; 
    volatile int done = 0; 
    clock_t start = clock(); 
    volatile clock_t now; 

    rc = fork(); 
    if (rc == 0) { //child 
    while (true) { 
     cout << "child doing work" << endl; 
     mtx.lock(); 
     now = clock() - start; 
     if (done) { 
     mtx.unlock(); 
     break; 
     } 
     if (now >= 200000 && !done) { 
     done = 1; 
     cout << "done by child at " << now << endl; 
     mtx.unlock(); 
     break; 
     } 
     cout << "child at " << now << endl; 
     mtx.unlock(); 
    } 
    _exit(0); 
    } 
    else { // parent 
    while (true) { 
     cout << "parent doing work" << endl; 
     mtx.lock(); 
     now = clock() - start; 
     if (done) { 
     mtx.unlock(); 
     break; 
     } 
     if (now >= 200000 && !done) { 
     done = 1; 
     cout << "done by parent at " << now << endl; 
     mtx.unlock(); 
     break; 
     } 
     cout << "parent at " << now << endl; 
     mtx.unlock(); 
    } 
    } 
    return 0; 
} 
+2

'volatile' не делает то, что, по вашему мнению, использует std :: atomic <>. И вы не тестируете потоки, а процессы - 'fork' создает новый процесс, а не новый поток в вашем процессе. –

+4

У вас нет двух потоков, но родительский и дочерний процессы (это совершенно разные). –

+2

Пожалуйста, прекратите использование 'volatile'. Для 'fork' и процессов вы можете прочитать http://beej.us/guide/bgipc/ для IPC. – knivil

ответ

5

Multi-процессы

Ваш код мульти-процессы и не многопоточности: fork() создаст новый отдельный процесс, дублируя вызывающий процесс.

Последствие: В момент дублирования все переменные содержат одно и то же значение в обоих процессах. Но каждый процесс имеет свою собственную копию, поэтому переменная, модифицированная родителем, не будет обновляться в адресном пространстве ребенка наоборот.

Если вы хотите, чтобы разделять переменные между процессами, вы должны взглянуть на this SO question

многопоточного

Для реальной многопоточности, вы должны использовать std::thread. И забудьте об изменчивости, потому что это не потокобезопасность. Вместо этого используйте <atomic>, как описано в этом awesome video.

Вот с первой попытки:

#include <iostream> 
#include <mutex> 
#include <thread> 
#include <atomic> 
#include <time.h> 

using namespace std; 

void child (atomic<int>& done, atomic<clock_t>& now, clock_t start) 
{ 
    while (!done) { 
     cout << "child doing work" << endl; 
     now = clock() - start; 
     if (now >= 2000 && !done) { 
      done = 1; 
      cout << "done by child at " << now << endl; 
     } 
     cout << "child at " << now << endl; 
     this_thread::yield(); 
    } 
} 

void parent (atomic<int>& done, atomic<clock_t>& now, clock_t start) 
{ 
    while (!done) { 
     cout << "parent doing work" << endl; 
     now = clock() - start; 
     if (now >= 2000 && !done) { 
     done = 1; 
     cout << "done by parent at " << now << endl; 
     } 
     cout << "parent at " << now << endl; 
     this_thread::yield(); 
    } 
} 

int main() { 
    atomic<int> done{0}; 
    clock_t start = clock(); 
    atomic<clock_t> now; 

    thread t(child, std::ref(done), std::ref(now), start); // attention, without ref, you get clones 
    parent (done, now, start); 
    t.join(); 

    return 0; 
} 

Обратите внимание, что вам не нужно, чтобы защитить атомарный доступ с помощью мьютекса, и что, если вы хотите сделать, lock_guard бы рекомендовал альтернативы.

Этот пример, конечно, довольно слабый, потому что если вы проверите атомную переменную, если условие if, это значение уже может быть изменено при вводе if-блока. Это не вызывает проблемы в вашей логике, где «сделано» означает «сделано». Но если вам понадобится более осторожный подход,
compare_exchange_weak() или compare_exchange_strong() может помочь.

+0

Спасибо за пример. Я получил его, чтобы работать вскоре после прочтения вышеприведенных комментариев, и я вспомнил опубликование комментария, говорящего об этом. В любом случае, у меня есть следующий вопрос. Мое решение очень похоже на ваше решение. Сначала, в main(), я помещаю родительскую строку перед дочерней строкой, выходы предполагали, что родительский процесс всегда был выполнен первым, а ребенок только должен был выполнить после того, как родитель был сделан; затем я поменял эти 2 строки, и он работал, как ожидалось. Так что, обмениваясь вокруг этих двух строк, действительно влияет на порядок исполнения так, как я упоминал? Если да, то почему? Спасибо заранее :) –

+0

У вас всегда есть хотя бы один поток, который выполнялся: основной поток. Он выполняет инструкции в порядке сортировки, поэтому, если он встретит вызов родителя, он выполнит этот вызов функции. Только когда вы создаете и инициируете объект потока, поток декодера будет выполняться параллельно (насколько это возможно). Поэтому, если вы создадите этот объект, при первом порядке родитель должен закончиться при запуске второго потока. Основной поток будет вежливо ждать, и это будет происходить. С другим порядком начинается второй поток и выполняется дочерний процесс, когда основной поток выполняет родительский. – Christophe

+0

Мой комментарий довольно лог, но попробуйте сделать диаграмму последовательности из него, и я думаю, что он станет понятнее – Christophe

Смежные вопросы