Ví dụ đơn giản về luồng trong C ++


391

Ai đó có thể đăng một ví dụ đơn giản về việc bắt đầu hai luồng (Hướng đối tượng) trong C ++.

Tôi đang tìm kiếm các đối tượng luồng C ++ thực tế mà tôi có thể mở rộng các phương thức chạy trên (hoặc một cái gì đó tương tự) thay vì gọi một thư viện luồng kiểu C.

Tôi đã bỏ qua bất kỳ yêu cầu cụ thể nào của HĐH với hy vọng bất cứ ai trả lời sẽ trả lời với các thư viện đa nền tảng để sử dụng. Bây giờ tôi chỉ nói rõ ràng thôi.


Câu trả lời:


561

Tạo một hàm mà bạn muốn luồng thực thi, ví dụ:

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

Bây giờ tạo threadđối tượng mà cuối cùng sẽ gọi hàm trên như vậy:

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

(Bạn cần #include <thread>truy cập vào std::threadlớp)

Các đối số của hàm tạo là hàm mà luồng sẽ thực thi, theo sau là các tham số của hàm. Các chủ đề được tự động bắt đầu khi xây dựng.

Nếu sau này bạn muốn đợi luồng hoàn thành thực thi chức năng, hãy gọi:

t1.join(); 

(Tham gia có nghĩa là luồng đã gọi luồng mới sẽ đợi luồng mới hoàn thành thực thi, trước khi nó tiếp tục thực hiện chính nó).


Mật mã

#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();
}

Thêm thông tin về std :: thread tại đây

  • Trên GCC, biên dịch với -std=c++0x -pthread.
  • Điều này sẽ hoạt động cho bất kỳ hệ điều hành nào, được cấp trình biên dịch của bạn hỗ trợ tính năng này (C ++ 11).

4
@ Preza8 VS10 không hỗ trợ nhiều tính năng trong C ++ 11. Chủ đề hỗ trợ VS12 và VS13.
jodag

3
Trong nhận xét "Phiên bản GCC dưới 4.7, hãy sử dụng -std=c++0x(thay vì -std=c++0x)" Tôi tin rằng "c ++ 0x" thứ hai thay vào đó là "c ++ 11", nhưng điều đó không thể thay đổi vì chỉnh sửa quá nhỏ.
zentrunix

1
@MasterMastic Có gì khác biệt nếu thay vì std :: thread t1 (task1, "Hello"), chúng tôi sử dụng std :: thread t1 (& task1, "Hello"), chuyển tham chiếu của hàm? Chúng ta có nên khai báo hàm như một con trỏ trong trường hợp này không?
dùng2452253

3
@ user2452253 Nếu bạn chuyển "task1", ​​bạn chuyển một con trỏ tới hàm bạn muốn thực thi (điều này tốt). Nếu bạn chuyển "& task1", ​​bạn chuyển một con trỏ đến một con trỏ của hàm và kết quả có thể sẽ không đẹp và rất không xác định (nó sẽ giống như cố gắng thực hiện lần lượt từng lệnh ngẫu nhiên. Với xác suất đúng bạn có thể mở cổng vào địa ngục). Bạn thực sự chỉ muốn truyền con trỏ hàm (một con trỏ có giá trị của địa chỉ nơi hàm bắt đầu). Nếu điều đó không rõ ràng, hãy cho tôi biết, và may mắn nhất!
MasterMastic

2
@cquilguy Vâng, cách đúng đắn để làm điều đó là vượt qua một con trỏ của hàm bạn muốn chạy. Trong trường hợp này chức năng là task1. Vì vậy, con trỏ hàm cũng được ký hiệu là task1. Nhưng cảm ơn bạn đã đưa ra điều này bởi vì tôi tin rằng tôi đã sai và nó tương đương với &task1nó, vì vậy việc bạn chọn viết hình thức nào không quan trọng (nếu vậy, tôi đoán rằng con trỏ đến con trỏ của hàm là &&task1- điều đó sẽ tệ ). Câu hỏi liên quan .
MasterMastic

80

Về mặt kỹ thuật, bất kỳ đối tượng nào như vậy cũng sẽ được xây dựng trên thư viện luồng kiểu C vì C ++ chỉ chỉ định một std::threadmô hình chứng khoán trong c ++ 0x, đã bị đóng đinh và chưa được triển khai. Vấn đề có phần hệ thống, về mặt kỹ thuật, mô hình bộ nhớ c ++ hiện tại không đủ nghiêm ngặt để cho phép các ngữ nghĩa được xác định rõ ràng cho tất cả các trường hợp 'xảy ra trước'. Hans Boehm đã viết một bài báo về chủ đề này một thời gian trước đây và là công cụ để đưa ra tiêu chuẩn c ++ 0x về chủ đề này.

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

Điều đó nói rằng có một số thư viện C ++ đa nền tảng hoạt động tốt trong thực tế. Các khối xây dựng luồng của Intel chứa một đối tượng luồng tbb :: gần đúng với tiêu chuẩn c ++ 0x và Boost có thư viện luồng boost :: hoạt động tương tự.

http://www.threadingbuildingblocks.org/

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

Sử dụng boost :: thread bạn sẽ nhận được một cái gì đó như:

#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
Boost thread rất tuyệt - vấn đề duy nhất của tôi là bạn không thể (khi tôi sử dụng lần cuối) thực sự truy cập vào xử lý luồng gốc bên dưới vì nó là một thành viên lớp riêng! Có một TẤN thứ trong win32 mà bạn cần xử lý, vì vậy chúng tôi đã điều chỉnh nó để xử lý công khai.
Orion Edwards

4
Một vấn đề khác với boost :: thread là khi tôi nhớ bạn không có quyền tự do đặt kích thước ngăn xếp của luồng mới - một tính năng cũng không được bao gồm trong tiêu chuẩn c ++ 0x.
Edward KMett

Mã đó mang lại cho tôi một ngoại lệ boost :: không thể sao chép cho dòng này: thread thread_1 = thread (task1); Có lẽ vì tôi đang sử dụng phiên bản cũ hơn (1.32).
Frank

task1 không được khai báo trong phạm vi này?
Jake Gaston

20

Ngoài ra còn có một thư viện POSIX cho các hệ điều hành POSIX. Kiểm tra tính tương thích

#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;

}

biên dịch với -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

Khi tìm kiếm một ví dụ về lớp C ++ gọi một trong các phương thức cá thể của chính nó trong một luồng mới, câu hỏi này xuất hiện, nhưng chúng tôi không thể sử dụng bất kỳ câu trả lời nào theo cách đó. Đây là một ví dụ thực hiện điều đó:

Lớp.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
    }
}

Lưu ý rằng ví dụ này không vào được mutex hoặc khóa.


2
Cảm ơn đã đăng bài này. Lưu ý rằng hình thức chung của việc gọi một luồng trên phương thức thể hiện giống như thế này: Foo f; std :: thread t (& Foo :: Run, & f, args ...); (trong đó Foo là một lớp có 'Run ()' là hàm thành viên).
Jay Elston

Cảm ơn đã đăng bài này. Tôi thực sự không hiểu tại sao tất cả các ví dụ luồng sử dụng các chức năng toàn cầu.
hkBattousai

9

Trừ khi ai đó muốn một chức năng riêng biệt trong bảng tên toàn cầu, chúng ta có thể sử dụng các hàm lambda để tạo chủ đề.

Một trong những lợi thế chính của việc tạo luồng bằng lambda là chúng ta không cần truyền tham số cục bộ làm danh sách đối số. Chúng ta có thể sử dụng danh sách chụp cho cùng và thuộc tính đóng của lambda sẽ đảm nhiệm vòng đời.

Đây là một mã mẫu

int main() {
    int localVariable = 100;

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

    th.join();

    return 0;
}

Cho đến nay, tôi đã thấy C ++ lambdas là cách tốt nhất để tạo chủ đề, đặc biệt là cho các chức năng chủ đề đơn giản hơn


8

Nó phần lớn phụ thuộc vào thư viện bạn quyết định sử dụng. Chẳng hạn, nếu bạn sử dụng thư viện wxWidgets, việc tạo ra một luồng sẽ như thế này:

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;
}

Nếu luồng chính của bạn gọi phương thức CreatThread, bạn sẽ tạo một luồng mới sẽ bắt đầu thực thi mã trong phương thức "Entry" của bạn. Bạn sẽ phải giữ một tham chiếu đến chuỗi trong hầu hết các trường hợp để tham gia hoặc dừng nó. Thêm thông tin ở đây: tài liệu wxThread


1
Bạn quên xóa chủ đề. Có lẽ bạn có nghĩa là để tạo ra một chủ đề tách ra ?
aib

1
Đúng vậy, tôi đã trích xuất mã từ một số mã tôi hiện đang làm việc và tất nhiên tham chiếu đến chuỗi được lưu trữ ở đâu đó để tham gia, dừng và xóa nó sau này. Cảm ơn. :)
LorenzCK
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.