Tập hợp chủ đề trong C ++ 11


130

Các câu hỏi liên quan :

Giới thiệu về C ++ 11:

Giới thiệu về Boost:


Làm thế nào để tôi có được một nhóm các chủ đề để gửi các tác vụ đến , mà không tạo và xóa chúng nhiều lần? Điều này có nghĩa là các chủ đề liên tục để đồng bộ hóa mà không cần tham gia.


Tôi có mã trông như thế này:

namespace {
  std::vector<std::thread> workers;

  int total = 4;
  int arr[4] = {0};

  void each_thread_does(int i) {
    arr[i] += 2;
  }
}

int main(int argc, char *argv[]) {
  for (int i = 0; i < 8; ++i) { // for 8 iterations,
    for (int j = 0; j < 4; ++j) {
      workers.push_back(std::thread(each_thread_does, j));
    }
    for (std::thread &t: workers) {
      if (t.joinable()) {
        t.join();
      }
    }
    arr[4] = std::min_element(arr, arr+4);
  }
  return 0;
}

Thay vì tạo và nối các luồng mỗi lần lặp, tôi muốn gửi các tác vụ đến các luồng nhân viên của mình mỗi lần lặp và chỉ tạo chúng một lần.


1
đây là một câu hỏi liên quan và câu trả lời của tôi
didierc

1
nghĩ về việc sử dụng tbb (đó là Intel, nhưng miễn phí & nguồn mở và thực hiện chính xác những gì bạn muốn: bạn chỉ cần gửi các nhiệm vụ (chia hết đệ quy) và không lo lắng về các luồng)?
Walter

2
Dự án FOSS này là nỗ lực của tôi để tạo một thư viện nhóm luồng, kiểm tra xem nếu bạn muốn. -> code.google.com/p/threadpool11
Etherealone

Có gì sai khi sử dụng tbb?
Walter

Câu trả lời:


84

Bạn có thể sử dụng Thư viện nhóm chủ đề C ++, https://github.com/vit-vit/ctpl .

Sau đó, mã của bạn đã viết có thể được thay thế bằng sau

#include <ctpl.h>  // or <ctpl_stl.h> if ou do not have Boost library

int main (int argc, char *argv[]) {
    ctpl::thread_pool p(2 /* two threads in the pool */);
    int arr[4] = {0};
    std::vector<std::future<void>> results(4);
    for (int i = 0; i < 8; ++i) { // for 8 iterations,
        for (int j = 0; j < 4; ++j) {
            results[j] = p.push([&arr, j](int){ arr[j] +=2; });
        }
        for (int j = 0; j < 4; ++j) {
            results[j].get();
        }
        arr[4] = std::min_element(arr, arr + 4);
    }
}

Bạn sẽ nhận được số lượng chủ đề mong muốn và sẽ không tạo và xóa chúng nhiều lần trên các lần lặp.


11
Đây phải là câu trả lời; thư viện C ++ 11 đơn tiêu đề, dễ đọc, đơn giản, ngắn gọn và tuân thủ tiêu chuẩn. Công việc tuyệt vời
Jonathan H

@ vit-vit bạn có thể cho một ví dụ với một chức năng xin vui lòng? Làm thế nào để bạn đẩy một chức năng thành viên lớp tạiresults[j] = p.push([&arr, j](int){ arr[j] +=2; });
Hani Goc

1
@HaniGoc Chỉ cần nắm bắt ví dụ bằng cách tham khảo.
Jonathan H

@ vit-vit Gửi cho bạn một yêu cầu kéo để cải thiện phiên bản STL.
Jonathan H

@ vit-vit: Thật khó để liên hệ với người bảo trì thư viện đó bằng các câu hỏi, gợi ý gợi ý.
einpoklum

82

Điều này được sao chép từ câu trả lời của tôi sang một bài viết rất giống nhau, hy vọng nó có thể giúp:

1) Bắt đầu với số lượng luồng tối đa mà hệ thống có thể hỗ trợ:

int Num_Threads =  thread::hardware_concurrency();

2) Để triển khai luồng dữ liệu hiệu quả, một khi các luồng được tạo theo Num_Threads, tốt hơn là không tạo các luồng mới hoặc phá hủy các luồng cũ (bằng cách nối). Sẽ có hình phạt hiệu suất, thậm chí có thể làm cho ứng dụng của bạn chạy chậm hơn phiên bản nối tiếp.

Mỗi luồng C ++ 11 phải được chạy trong chức năng của chúng với một vòng lặp vô hạn, liên tục chờ đợi các tác vụ mới để lấy và chạy.

Dưới đây là cách gắn chức năng đó vào nhóm luồng:

int Num_Threads = thread::hardware_concurrency();
vector<thread> Pool;
for(int ii = 0; ii < Num_Threads; ii++)
{  Pool.push_back(thread(Infinite_loop_function));}

3) Infinite_loop_feft

Đây là một vòng lặp "while (true)" đang chờ hàng đợi tác vụ

void The_Pool:: Infinite_loop_function()
{
    while(true)
    {
        {
            unique_lock<mutex> lock(Queue_Mutex);

            condition.wait(lock, []{return !Queue.empty() || terminate_pool});
            Job = Queue.front();
            Queue.pop();
        }
        Job(); // function<void()> type
    }
};

4) Tạo một chức năng để thêm công việc vào Hàng đợi của bạn

void The_Pool:: Add_Job(function<void()> New_Job)
{
    {
        unique_lock<mutex> lock(Queue_Mutex);
        Queue.push(New_Job);
    }
    condition.notify_one();
}

5) Liên kết một hàm tùy ý vào Hàng đợi của bạn

Pool_Obj.Add_Job(std::bind(&Some_Class::Some_Method, &Some_object));

Khi bạn tích hợp các thành phần này, bạn có nhóm luồng động của riêng bạn. Những chủ đề này luôn luôn chạy, chờ công việc để làm.

Tôi xin lỗi nếu có một số lỗi cú pháp, tôi đã gõ các mã này và tôi có một bộ nhớ xấu. Xin lỗi rằng tôi không thể cung cấp cho bạn mã nhóm hoàn chỉnh, điều đó sẽ vi phạm tính toàn vẹn công việc của tôi.

Chỉnh sửa: để chấm dứt nhóm, gọi phương thức shutdown ():

XXXX::shutdown(){
{
    unique_lock<mutex> lock(threadpool_mutex);
    terminate_pool = true;} // use this flag in condition.wait

    condition.notify_all(); // wake up all threads.

    // Join all threads.
    for(std::thread &every_thread : thread_vector)
    {   every_thread.join();}

    thread_vector.clear();  
    stopped = true; // use this flag in destructor, if not set, call shutdown() 
}

Làm thế nào để bạn có một vectơ <thread> khi thread (const thread &) = xóa?
Christopher Pisz

1
@ChristopherPisz std::vectorkhông yêu cầu các yếu tố của nó có thể sao chép được. Bạn có thể sử dụng vectơ với các loại di chuyển chỉ ( unique_ptr, thread, future, vv).
Daniel Langr

trong ví dụ trên của bạn, làm thế nào để bạn dừng hồ bơi? Cũng nên condition.waittìm một biến stop_và kiểm tra if (stop_ == true) { break;}?
Giăng

@ John, vui lòng xem phương thức tắt máy ở trên.
Tiến sĩ AP EcE

2
Trong shutdown (), nó phải là thread_vector.clear (); thay vì thread_vector.empty (); Chính xác?
sudheerbb

63

Nhóm các luồng có nghĩa là tất cả các luồng của bạn đang chạy, mọi lúc - nói cách khác, hàm luồng không bao giờ trả về. Để cung cấp cho các chủ đề một cái gì đó có ý nghĩa, bạn phải thiết kế một hệ thống giao tiếp liên luồng, cả hai đều nhằm mục đích nói cho chủ đề rằng có việc cần làm, cũng như để truyền đạt dữ liệu công việc thực tế.

Thông thường, điều này sẽ liên quan đến một số loại cấu trúc dữ liệu đồng thời và mỗi luồng có lẽ sẽ ngủ trên một loại biến điều kiện nào đó, sẽ được thông báo khi có việc phải làm. Khi nhận được thông báo, một hoặc một số luồng thức dậy, khôi phục một tác vụ từ cấu trúc dữ liệu đồng thời, xử lý nó và lưu trữ kết quả theo cách tương tự.

Sau đó, chủ đề sẽ tiếp tục kiểm tra xem có còn nhiều việc phải làm hay không, và nếu không quay trở lại giấc ngủ.

Kết quả cuối cùng là bạn phải tự thiết kế tất cả những thứ này, vì không có khái niệm tự nhiên nào về "công việc" được áp dụng phổ biến. Đó là một chút công việc, và có một số vấn đề tinh tế bạn phải giải quyết. (Bạn có thể lập trình trong Go nếu bạn thích một hệ thống chăm sóc quản lý luồng cho bạn đằng sau hậu trường.)


11
"Bạn phải tự thiết kế tất cả những thứ này" <- đó là điều tôi đang cố gắng tránh làm. Goroutines có vẻ tuyệt vời, mặc dù.
Yktula

2
@Yktula: Vâng, đó là một nhiệm vụ không tầm thường. Nó thậm chí không rõ ràng từ bài viết của bạn loại công việc bạn muốn thực hiện, và đó là cơ bản sâu sắc cho giải pháp. Bạn có thể triển khai Go trong C ++, nhưng đó sẽ là một điều rất cụ thể và một nửa số người sẽ phàn nàn rằng họ muốn một cái gì đó khác biệt.
Kerrek SB

19

Một luồng trong lõi là một tập hợp các luồng tất cả được liên kết với một hàm làm việc như một vòng lặp sự kiện. Các luồng này sẽ chờ đợi một tác vụ được thực thi hoặc chấm dứt riêng của chúng.

Công việc xử lý luồng là cung cấp giao diện để gửi công việc, xác định (và có thể sửa đổi) chính sách chạy các công việc này (quy tắc lập lịch, khởi tạo luồng, kích thước của nhóm) và theo dõi trạng thái của luồng và tài nguyên liên quan.

Vì vậy, đối với nhóm đa năng, người ta phải bắt đầu bằng cách xác định nhiệm vụ là gì, cách thức khởi chạy, bị gián đoạn, kết quả là gì (xem khái niệm về lời hứa và tương lai cho câu hỏi đó), loại chủ đề nào sẽ phải trả lời để, họ sẽ xử lý chúng như thế nào, làm thế nào những sự kiện này sẽ bị phân biệt đối xử với những sự kiện được xử lý bởi các nhiệm vụ. Điều này có thể trở nên khá phức tạp như bạn có thể thấy và áp đặt các hạn chế về cách các luồng sẽ hoạt động, khi giải pháp ngày càng liên quan nhiều hơn.

Công cụ hiện tại để xử lý các sự kiện là khá đơn giản (*): nguyên thủy như mutexes, biến điều kiện và một vài trừu tượng trên đó (khóa, rào cản). Nhưng trong một số trường hợp, những tóm tắt này có thể không phù hợp (xem câu hỏi liên quan này ), và người ta phải quay trở lại sử dụng các nguyên thủy.

Các vấn đề khác cũng phải được quản lý:

  • tín hiệu
  • tôi / o
  • phần cứng (ái lực bộ xử lý, thiết lập không đồng nhất)

Làm thế nào những điều này sẽ diễn ra trong thiết lập của bạn?

Câu trả lời này cho một câu hỏi tương tự chỉ ra một triển khai hiện có có nghĩa là để tăng và stl.

Tôi đã đưa ra một triển khai rất thô sơ của một luồng cho một câu hỏi khác, không giải quyết được nhiều vấn đề được nêu ở trên. Bạn có thể muốn xây dựng trên nó. Bạn cũng có thể muốn có một cái nhìn về các khung hiện có trong các ngôn ngữ khác, để tìm cảm hứng.


(*) Tôi không thấy đó là một vấn đề, hoàn toàn ngược lại. Tôi nghĩ đó là tinh thần của C ++ được thừa hưởng từ C.


4
Follwoing [PhD EcE](https://stackoverflow.com/users/3818417/phd-ece) suggestion, I implemented the thread pool:

hàm_pool.h

#pragma once
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cassert>

class Function_pool
{

private:
    std::queue<std::function<void()>> m_function_queue;
    std::mutex m_lock;
    std::condition_variable m_data_condition;
    std::atomic<bool> m_accept_functions;

public:

    Function_pool();
    ~Function_pool();
    void push(std::function<void()> func);
    void done();
    void infinite_loop_func();
};

function_pool.cpp

#include "function_pool.h"

Function_pool::Function_pool() : m_function_queue(), m_lock(), m_data_condition(), m_accept_functions(true)
{
}

Function_pool::~Function_pool()
{
}

void Function_pool::push(std::function<void()> func)
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_function_queue.push(func);
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    lock.unlock();
    m_data_condition.notify_one();
}

void Function_pool::done()
{
    std::unique_lock<std::mutex> lock(m_lock);
    m_accept_functions = false;
    lock.unlock();
    // when we send the notification immediately, the consumer will try to get the lock , so unlock asap
    m_data_condition.notify_all();
    //notify all waiting threads.
}

void Function_pool::infinite_loop_func()
{
    std::function<void()> func;
    while (true)
    {
        {
            std::unique_lock<std::mutex> lock(m_lock);
            m_data_condition.wait(lock, [this]() {return !m_function_queue.empty() || !m_accept_functions; });
            if (!m_accept_functions && m_function_queue.empty())
            {
                //lock will be release automatically.
                //finish the thread loop and let it join in the main thread.
                return;
            }
            func = m_function_queue.front();
            m_function_queue.pop();
            //release the lock
        }
        func();
    }
}

main.cpp

#include "function_pool.h"
#include <string>
#include <iostream>
#include <mutex>
#include <functional>
#include <thread>
#include <vector>

Function_pool func_pool;

class quit_worker_exception : public std::exception {};

void example_function()
{
    std::cout << "bla" << std::endl;
}

int main()
{
    std::cout << "stating operation" << std::endl;
    int num_threads = std::thread::hardware_concurrency();
    std::cout << "number of threads = " << num_threads << std::endl;
    std::vector<std::thread> thread_pool;
    for (int i = 0; i < num_threads; i++)
    {
        thread_pool.push_back(std::thread(&Function_pool::infinite_loop_func, &func_pool));
    }

    //here we should send our functions
    for (int i = 0; i < 50; i++)
    {
        func_pool.push(example_function);
    }
    func_pool.done();
    for (unsigned int i = 0; i < thread_pool.size(); i++)
    {
        thread_pool.at(i).join();
    }
}

2
Cảm ơn! Điều này thực sự giúp tôi bắt đầu với các hoạt động luồng song song. Tôi đã kết thúc bằng cách sử dụng một phiên bản sửa đổi một chút của việc thực hiện của bạn.
Robbie Capps

3

Một cái gì đó như thế này có thể giúp đỡ (lấy từ một ứng dụng đang hoạt động).

#include <memory>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

struct thread_pool {
  typedef std::unique_ptr<boost::asio::io_service::work> asio_worker;

  thread_pool(int threads) :service(), service_worker(new asio_worker::element_type(service)) {
    for (int i = 0; i < threads; ++i) {
      auto worker = [this] { return service.run(); };
      grp.add_thread(new boost::thread(worker));
    }
  }

  template<class F>
  void enqueue(F f) {
    service.post(f);
  }

  ~thread_pool() {
    service_worker.reset();
    grp.join_all();
    service.stop();
  }

private:
  boost::asio::io_service service;
  asio_worker service_worker;
  boost::thread_group grp;
};

Bạn có thể sử dụng nó như thế này:

thread_pool pool(2);

pool.enqueue([] {
  std::cout << "Hello from Task 1\n";
});

pool.enqueue([] {
  std::cout << "Hello from Task 2\n";
});

Hãy nhớ rằng việc phát minh lại một cơ chế xếp hàng không đồng bộ hiệu quả là không tầm thường.

Boost :: asio :: io_service là một triển khai rất hiệu quả hoặc thực sự là một tập hợp các trình bao bọc dành riêng cho nền tảng (ví dụ: nó bao bọc các cổng hoàn thành I / O trên Windows).


2
Đó có phải là sự tăng cường cần thiết với C ++ 11 không? Sẽ không, nói, một std::threadđủ?
einpoklum

Không có tương đương trong stdcho boost::thread_group. boost::thread_grouplà một tập hợp các boost::threadtrường hợp. Nhưng tất nhiên, nó rất dễ dàng để thay thế boost::thread_groupvới một vectorcủa std::threads.
rustyx

3

Chỉnh sửa: Điều này bây giờ yêu cầu C ++ 17 và các khái niệm. (Kể từ ngày 9/12/16, chỉ có g ++ 6.0+ là đủ.)

Mặc dù vậy, việc khấu trừ mẫu chính xác hơn rất nhiều vì vậy, nó xứng đáng với nỗ lực để có được một trình biên dịch mới hơn. Tôi chưa tìm thấy một hàm yêu cầu đối số mẫu rõ ràng.

Bây giờ nó cũng nhận bất kỳ đối tượng có thể gọi thích hợp nào ( và vẫn còn an toàn kiểu !!! ).

Bây giờ nó cũng bao gồm nhóm luồng ưu tiên luồng màu xanh lá cây tùy chọn sử dụng cùng API. Lớp này chỉ là POSIX. Nó sử dụng ucontext_tAPI để chuyển đổi tác vụ không gian người dùng.


Tôi đã tạo ra một thư viện đơn giản cho việc này. Một ví dụ về việc sử dụng được đưa ra dưới đây. (Tôi đang trả lời điều này vì đó là một trong những điều tôi tìm thấy trước khi tôi quyết định cần phải tự viết nó.)

bool is_prime(int n){
  // Determine if n is prime.
}

int main(){
  thread_pool pool(8); // 8 threads

  list<future<bool>> results;
  for(int n = 2;n < 10000;n++){
    // Submit a job to the pool.
    results.emplace_back(pool.async(is_prime, n));
  }

  int n = 2;
  for(auto i = results.begin();i != results.end();i++, n++){
    // i is an iterator pointing to a future representing the result of is_prime(n)
    cout << n << " ";
    bool prime = i->get(); // Wait for the task is_prime(n) to finish and get the result.
    if(prime)
      cout << "is prime";
    else
      cout << "is not prime";
    cout << endl;
  }  
}

Bạn có thể chuyển asyncbất kỳ hàm nào với bất kỳ giá trị trả về (hoặc void) nào và bất kỳ đối số (hoặc không) nào và nó sẽ trả về một giá trị tương ứng std::future. Để nhận kết quả (hoặc chỉ cần đợi cho đến khi hoàn thành nhiệm vụ), bạn gọiget() về tương lai.

Đây là github: https://github.com/Tyler-Hardin/thread_pool .


1
Trông thật tuyệt vời, nhưng sẽ thật tuyệt khi được so sánh với tiêu đề của vit-vit!
Jonathan H

1
@ Sh3ljohn, từ việc liếc nhìn nó, có vẻ như chúng giống nhau về API. vit-vit sử dụng hàng đợi không khóa của boost, tốt hơn so với của tôi. . sử dụng đơn giản hơn cho những người không biết họ đang làm gì. (Ví dụ: github.com/Tyler-Hardin/thread_pool/issues/1 )
Tyler

Anh ấy / cô ấy cũng có một giải pháp duy nhất mà tôi đã sử dụng trong vài giờ qua, ban đầu nó trông phức tạp hơn so với các con trỏ được chia sẻ ở khắp mọi nơi, nhưng điều này thực sự cần thiết để xử lý thay đổi kích thước nóng đúng cách.
Jonathan H

@ Sh3ljohn, ah, tôi không nhận thấy thay đổi kích thước nóng. Điều đó thật tuyệt. Tôi đã chọn không lo lắng về nó bởi vì nó không thực sự trong trường hợp sử dụng dự định. (Tôi không thể nghĩ về trường hợp tôi muốn thay đổi kích thước cá nhân, nhưng đó có thể là do thiếu trí tưởng tượng.)
Tyler

1
Ví dụ về trường hợp sử dụng: bạn đang chạy API RESTful trên máy chủ và cần giảm tạm thời việc phân bổ tài nguyên cho mục đích bảo trì mà không cần phải tắt hoàn toàn dịch vụ.
Jonathan H

3

Đây là một triển khai nhóm luồng khác rất đơn giản, dễ hiểu và dễ sử dụng, chỉ sử dụng thư viện chuẩn C ++ 11 và có thể được xem hoặc sửa đổi cho mục đích sử dụng của bạn, nên là một khởi đầu tốt nếu bạn muốn sử dụng luồng bể bơi:

https://github.com/progschj/ThreadPool


3

Bạn có thể sử dụng thread_pool từ thư viện boost:

void my_task(){...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    boost::asio::thread_pool pool(threadNumbers);

    // Submit a function to the pool.
    boost::asio::post(pool, my_task);

    // Submit a lambda object to the pool.
    boost::asio::post(pool, []() {
      ...
    });
}

Bạn cũng có thể sử dụng threadpool từ cộng đồng nguồn mở:

void first_task() {...}    
void second_task() {...}

int main(){
    int threadNumbers = thread::hardware_concurrency();
    pool tp(threadNumbers);

    // Add some tasks to the pool.
    tp.schedule(&first_task);
    tp.schedule(&second_task);
}

1

Một luồng không có phụ thuộc bên ngoài STL là hoàn toàn có thể. Gần đây tôi đã viết một thư viện threadpool chỉ tiêu đề nhỏ để giải quyết vấn đề chính xác tương tự. Nó hỗ trợ thay đổi kích thước nhóm động (thay đổi số lượng công nhân khi chạy), chờ, dừng, tạm dừng, tiếp tục, v.v. Tôi hy vọng bạn thấy nó hữu dụng.


có vẻ như bạn đã xóa tài khoản github của mình (hoặc liên kết sai). Bạn đã di chuyển mã này ở nơi khác?
rtpax

1
@rtpax Tôi đã chuyển repo - Tôi đã cập nhật câu trả lời để phản ánh điều đó.
cantordust
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.