Простой пример многопоточности в C ++


391

Может кто-нибудь опубликовать простой пример запуска двух (объектно-ориентированных) потоков в C ++.

Я ищу фактические объекты потока C ++, на которые я могу расширить методы выполнения (или что-то подобное), в отличие от вызова библиотеки потоков в стиле C.

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


Ответы:


563

Создайте функцию, которую вы хотите, чтобы поток выполнял, например:

void task1(std::string msg)
{
    std::cout << "task1 says: " << msg;
}

Теперь создайте threadобъект, который в конечном итоге вызовет функцию, описанную выше, следующим образом:

std::thread t1(task1, "Hello");

(Вам необходимо #include <thread>получить доступ к std::threadклассу)

Аргументы конструктора - это функция, которую будет выполнять поток, за которой следуют параметры функции. Поток автоматически запускается при строительстве.

Если позже вы захотите дождаться завершения потока, выполняющего функцию, вызовите:

t1.join(); 

(Присоединение означает, что поток, вызвавший новый поток, будет ожидать завершения нового потока, прежде чем он продолжит свое собственное выполнение).


Код

#include <string>
#include <iostream>
#include <thread>

using namespace std;

// The function we want to execute on the new thread.
void task1(string msg)
{
    cout << "task1 says: " << msg;
}

int main()
{
    // Constructs the new thread and runs it. Does not block execution.
    thread t1(task1, "Hello");

    // Do other things...

    // Makes the main thread wait for the new thread to finish execution, therefore blocks its own execution.
    t1.join();
}

Больше информации о std :: thread здесь

  • На GCC, скомпилировать с -std=c++0x -pthread.
  • Это должно работать для любой операционной системы, если ваш компилятор поддерживает эту функцию (C ++ 11).

4
@ Preza8 VS10 не поддерживает много функций в C ++ 11. VS12 и VS13 поддерживают резьбу.
Jodag

3
В комментарии «Версии GCC ниже 4.7, используйте -std=c++0x(вместо -std=c++0x)» Я считаю, что второй «c ++ 0x» должен быть вместо «c ++ 11», но это невозможно изменить, потому что редактирование слишком мало.
Zentrunix

1
@MasterMastic Какая разница, если вместо std :: thread t1 (task1, «Hello») мы используем std :: thread t1 (& task1, «Hello»), передавая ссылку на функцию? Должны ли мы объявить функцию как указатель в этом случае?
user2452253

3
@ user2452253 Если вы передадите «task1», вы передадите указатель на функцию, которую хотите выполнить (что хорошо). Если вы передадите «& task1», вы передадите указатель на указатель на функцию, и результаты, вероятно, не будут такими красивыми и не очень определенными (это все равно, что пытаться выполнять случайные инструкции одну за другой. С правильной вероятностью вы просто можете открыть ворота в ад). Вы действительно просто хотите передать указатель на функцию (указатель со значением адреса, с которого начинается функция). Если это не прояснит ситуацию, дайте мне знать, и удачи!
MasterMastic

2
@curiousguy Что ж, правильный способ сделать это - передать указатель на функцию, которую вы хотите запустить. В этом случае функция есть task1. Таким образом, указатель на функцию также обозначается как task1. Но спасибо за то, что вы подняли этот вопрос, потому что я считаю, что я был неправ, и это эквивалентно &task1, поэтому не имеет значения, какую форму вы решите написать (если так, я думаю, что указатель на указатель на функцию &&task1- это было бы плохо ). Актуальный вопрос .
MasterMastic

80

Ну, технически любой такой объект будет построен из библиотеки потоков в стиле C, потому что C ++ только что определил стандартную std::threadмодель в c ++ 0x, которая была просто завершена и еще не реализована. Проблема в некоторой степени системная, технически существующая модель памяти c ++ недостаточно строгая, чтобы обеспечить четкую семантику для всех случаев «происходит раньше». Некоторое время назад Ханс Бём написал статью на эту тему и сыграл важную роль в разработке стандарта c ++ 0x по этой теме.

http://www.hpl.hp.com/techreports/2004/HPL-2004-209.html

Тем не менее, есть несколько кроссплатформенных библиотек C ++, которые на практике работают просто отлично. Строительные блоки Intel содержат объект tbb :: thread, который близко соответствует стандарту c ++ 0x, а Boost имеет библиотеку boost :: thread, которая делает то же самое.

http://www.threadingbuildingblocks.org/

http://www.boost.org/doc/libs/1_37_0/doc/html/thread.html

Используя boost :: thread, вы получите что-то вроде:

#include <boost/thread.hpp>

void task1() { 
    // do stuff
}

void task2() { 
    // do stuff
}

int main (int argc, char ** argv) {
    using namespace boost; 
    thread thread_1 = thread(task1);
    thread thread_2 = thread(task2);

    // do other stuff
    thread_2.join();
    thread_1.join();
    return 0;
}

8
Ускорение потока - это здорово - моя единственная проблема была в том, что вы не могли (когда я в последний раз использовал его) на самом деле получить доступ к собственному дескриптору основного потока, так как он был членом частного класса! В win32 есть тонна вещей, для которых вам нужен дескриптор потока, поэтому мы настроили его, чтобы сделать его публичным.
Орион Эдвардс

4
Другая проблема с boost :: thread заключается в том, что, насколько я помню, у вас нет свободы устанавливать размер стека нового потока - функция, к сожалению, также не включенная в стандарт c ++ 0x.
Эдвард КМЕТТ

Этот код дает мне исключение boost :: noncopyable для этой строки: thread thread_1 = thread (task1); Может быть, это потому, что я использую старую версию (1.32).
Фрэнк

task1 не был объявлен в этой области?
Джейк Гастон

20

Существует также библиотека POSIX для операционных систем POSIX. Проверить совместимость

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <iostream>

void *task(void *argument){
      char* msg;
      msg = (char*)argument;
      std::cout<<msg<<std::endl;
}

int main(){
    pthread_t thread1, thread2;
    int i1,i2;
    i1 = pthread_create( &thread1, NULL, task, (void*) "thread 1");
    i2 = pthread_create( &thread2, NULL, task, (void*) "thread 2");

    pthread_join(thread1,NULL);
    pthread_join(thread2,NULL);
    return 0;

}

компилировать с -lpthread

http://en.wikipedia.org/wiki/POSIX_Threads


18
#include <thread>
#include <iostream>
#include <vector>
using namespace std;

void doSomething(int id) {
    cout << id << "\n";
}

/**
 * Spawns n threads
 */
void spawnThreads(int n)
{
    std::vector<thread> threads(n);
    // spawn n threads:
    for (int i = 0; i < n; i++) {
        threads[i] = thread(doSomething, i + 1);
    }

    for (auto& th : threads) {
        th.join();
    }
}

int main()
{
    spawnThreads(10);
}

13

При поиске примера класса C ++, который вызывает один из своих собственных методов экземпляра в новом потоке, возникает этот вопрос, но мы не смогли использовать ни один из этих ответов таким образом. Вот пример, который делает это:

Class.h

class DataManager
{
public:
    bool hasData;
    void getData();
    bool dataAvailable();
};

Class.cpp

#include "DataManager.h"

void DataManager::getData()
{
    // perform background data munging
    hasData = true;
    // be sure to notify on the main thread
}

bool DataManager::dataAvailable()
{
    if (hasData)
    {
        return true;
    }
    else
    {
        std::thread t(&DataManager::getData, this);
        t.detach(); // as opposed to .join, which runs on the current thread
    }
}

Обратите внимание, что этот пример не попадает в мьютекс или блокировку.


2
Спасибо за публикацию этого. Обратите внимание, что общая форма вызова потока в методе экземпляра выглядит примерно так: Foo f; std :: thread t (& Foo :: Run, & f, args ...); (где Foo является классом, в котором функция Run () имеет функцию-член).
Джей Элстон

Спасибо за публикацию этого. Я серьезно не понимаю, почему все примеры потоков используют глобальные функции.
hkBattousai

9

Если не требуется отдельная функция в глобальных пространствах имен, мы можем использовать лямбда-функции для создания потоков.

Одним из основных преимуществ создания потока с использованием лямбды является то, что нам не нужно передавать локальные параметры в виде списка аргументов. Мы можем использовать список захвата для того же самого, и свойство закрытия lambda позаботится о жизненном цикле.

Вот пример кода

int main() {
    int localVariable = 100;

    thread th { [=](){
        cout<<"The Value of local variable => "<<localVariable<<endl;
    }}

    th.join();

    return 0;
}

До сих пор я обнаружил, что лямбда-символы C ++ являются лучшим способом создания потоков, особенно для более простых функций потоков.


8

Это во многом зависит от библиотеки, которую вы решили использовать. Например, если вы используете библиотеку wxWidgets, создание потока будет выглядеть так:

class RThread : public wxThread {

public:
    RThread()
        : wxThread(wxTHREAD_JOINABLE){
    }
private:
    RThread(const RThread &copy);

public:
    void *Entry(void){
        //Do...

        return 0;
    }

};

wxThread *CreateThread() {
    //Create thread
    wxThread *_hThread = new RThread();

    //Start thread
    _hThread->Create();
    _hThread->Run();

    return _hThread;
}

Если ваш основной поток вызывает метод CreateThread, вы создадите новый поток, который начнет выполнять код в вашем методе «Entry». В большинстве случаев вам нужно будет сохранить ссылку на поток, чтобы присоединиться к нему или остановить его. Более подробная информация здесь: wxThread документация


1
Вы забыли удалить тему. Возможно, вы хотели создать отдельную тему?
AIB

1
Да, верно, я извлек код из некоторого кода, над которым я сейчас работаю, и, конечно, ссылка на поток хранится где-то, чтобы присоединиться, остановить и удалить его позже. Спасибо. :)
LorenzCK
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.