Простий приклад різьблення в C ++


391

Чи може хтось розмістити простий приклад запуску двох (об'єктно-орієнтованих) потоків у C ++.

Я шукаю фактичні об'єкти потоку C ++, на які я можу розширити методи запуску (або щось подібне) на відміну від виклику бібліотеки потоків у стилі С.

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


Відповіді:


561

Створіть функцію, для якої потрібно виконати потік, наприклад:

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 ::: що приблизно наближає стандарт c ++ 0x, а Boost має бібліотеку boost :: потоків, яка робить те саме.

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.
Едуард КМЕТТ

Цей код дає мені імпульс :: виключення, яке не можна скопіювати, для цього рядка: 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
    }
}

Зверніть увагу, що цей приклад не потрапляє в mutex або блокується.


2
Дякуємо, що опублікували це. Зауважте, що загальна форма виклику потоку методу екземпляра має щось подібне: Foo f; std :: потік t (& Foo :: Run, & f, args ...); (де Foo - клас, у якому функція-член має "Run ()".
Джей Елстон

Дякуємо, що опублікували це. Я серйозно не розумію, чому всі приклади ниток використовують глобальні функції.
hkBattousai

9

Якщо не потрібно окремої функції в глобальних просторах імен, ми можемо використовувати лямбда-функції для створення ниток.

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

Ось зразок коду

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, ви створите новий потік, який почне виконувати код у вашому методі "Введення". У більшості випадків вам доведеться зберегти посилання на потік, щоб приєднатись або зупинити його. Більше інформації тут: документація на wxThread


1
Ви забули видалити нитку. Можливо, ви мали намір створити відокремлену нитку?
аїб

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