เริ่มเธรดด้วยฟังก์ชันสมาชิก


294

ฉันพยายามที่จะสร้างที่มีฟังก์ชั่นสมาชิกที่เกิดการขัดแย้งใดและผลตอบแทนstd::thread voidฉันไม่สามารถหาไวยากรณ์ที่ใช้งานได้ - คอมไพเลอร์บ่นไม่ว่าจะเกิดอะไรขึ้น เป็นวิธีที่ถูกต้องในการใช้งานspawn()เพื่อให้ผลตอบแทนstd::threadที่ดำเนินการtest()คืออะไร?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};

1
คุณหมายถึงฟังก์ชั่นส่งกลับเป็นโมฆะเรียกว่าเป็นโมฆะหรือเพียงแค่ไม่มีพารามิเตอร์ใด ๆ คุณสามารถเพิ่มรหัสสำหรับสิ่งที่คุณพยายามจะทำอย่างไร
Zaid Amir

คุณผ่านการทดสอบแล้วหรือยัง (ฉันยังไม่ได้) รหัสของคุณดูเหมือนจะพึ่งพา RVO (return-value-optimzation) แต่ฉันไม่คิดว่าคุณควรจะทำเช่นนั้น ฉันคิดว่าการใช้std::move( std::thread(func) );ดีกว่าเพราะstd::threadไม่มีตัวสร้างสำเนา
RnMss

4
@RnMss: คุณสามารถพึ่งพา RVOได้การใช้งานstd::moveซ้ำซ้อนในกรณีนี้ - เป็นสิ่งที่ไม่จริงและไม่มีคอนสตรัคชันการคัดลอกคอมไพเลอร์จะให้ข้อผิดพลาดอยู่ดี
Qualia

คำตอบ:


367
#include <thread>
#include <iostream>

class bar {
public:
  void foo() {
    std::cout << "hello from member function" << std::endl;
  }
};

int main()
{
  std::thread t(&bar::foo, bar());
  t.join();
}

แก้ไข: บัญชีการแก้ไขของคุณคุณต้องทำเช่นนี้:

  std::thread spawn() {
    return std::thread(&blub::test, this);
  }

UPDATE:ฉันต้องการอธิบายเพิ่มเติมบางประเด็นบางส่วนถูกอภิปรายในความคิดเห็นด้วย

ไวยากรณ์ที่อธิบายไว้ข้างต้นมีการกำหนดในแง่ของนิยาม INVOKE (§20.8.2.1):

กำหนด INVOKE (f, t1, t2, ... , tN) ดังนี้:

  • (t1. * f) (t2, ... , tN) เมื่อ f เป็นตัวชี้ไปยังฟังก์ชันสมาชิกของคลาส T และ t1 เป็นวัตถุประเภท T หรือการอ้างอิงไปยังวัตถุประเภท T หรือการอ้างอิงถึง วัตถุประเภทที่ได้มาจาก T;
  • ((* t1). * f) (t2, ... , tN) เมื่อ f เป็นตัวชี้ไปยังฟังก์ชันสมาชิกของคลาส T และ t1 ไม่ใช่ประเภทที่อธิบายไว้ในรายการก่อนหน้า
  • t1. * f เมื่อ N == 1 และ f เป็นตัวชี้ไปยังข้อมูลสมาชิกของคลาส T และ t 1 เป็นวัตถุประเภท T หรือการ
    อ้างอิงไปยังวัตถุประเภท T หรือการอ้างอิงไปยังวัตถุ
    ประเภทที่ได้มาจาก T;
  • (* t1). * f เมื่อ N == 1 และ f เป็นตัวชี้ไปยังข้อมูลสมาชิกของคลาส T และ t 1 ไม่ใช่ประเภทที่อธิบายไว้ในรายการก่อนหน้า
  • f (t1, t2, ... , tN) ในกรณีอื่นทั้งหมด

ข้อเท็จจริงทั่วไปอื่น ๆ ที่ฉันต้องการชี้ให้เห็นคือโดยค่าเริ่มต้นตัวสร้างเธรดจะคัดลอกอาร์กิวเมนต์ทั้งหมดที่ส่งผ่านไป เหตุผลของเรื่องนี้คือข้อโต้แย้งอาจต้องอยู่ได้นานกว่าการเรียกเธรดการคัดลอกข้อโต้แย้งรับประกันได้ว่า แต่ถ้าคุณต้องการจริงๆผ่านการอ้างอิงคุณสามารถใช้สร้างขึ้นโดยstd::reference_wrapperstd::ref

std::thread (foo, std::ref(arg1));

โดยการทำเช่นนี้คุณสัญญาว่าคุณจะดูแลการรับประกันว่าข้อโต้แย้งจะยังคงอยู่เมื่อเธรดดำเนินการกับพวกเขา


หมายเหตุว่าทุกสิ่งที่กล่าวข้างต้นนอกจากนี้ยังสามารถนำไปใช้และstd::asyncstd::bind


1
อย่างน้อยก็ด้วยวิธีนี้มันรวบรวม แม้ว่าฉันจะไม่มีความคิดว่าทำไมคุณผ่านตัวอย่างเป็นอาร์กิวเมนต์ที่สอง
abergmeier

15
@LCID: รุ่นหลายข้อโต้แย้งของตัวสร้างการทำงานเป็นถ้าข้อโต้แย้งที่ถูกส่งผ่านไปยังstd::thread std::bindในการเรียกฟังก์ชั่นสมาชิกอาร์กิวเมนต์แรกที่จะstd::bindต้องเป็นตัวชี้การอ้างอิงหรือตัวชี้ที่ใช้ร่วมกันไปยังวัตถุประเภทที่เหมาะสม
Dave S

คุณจะเอามันไปจากที่นวกรรมิกทำหน้าที่เหมือนเป็นนัยbind? ฉันไม่พบที่ใดก็ได้
Kerrek SB

3
@ KerrekSB เปรียบเทียบ [thread.thread.constr] p4 กับ [func.bind.bind] p3 ความหมายนั้นค่อนข้างคล้ายคลึงกันซึ่งนิยามไว้ในแง่ของการปลอมของ INVOKE ซึ่งกำหนดวิธีการทำงานของสมาชิก
Jonathan Wakely

4
โปรดจำไว้ว่าฟังก์ชั่นสมาชิกคงที่ไม่เป็นพารามิเตอร์แรกใช้อินสแตนซ์ของระดับ (มันไม่สามารถมองเห็นได้สำหรับโปรแกรมเมอร์) ดังนั้นเมื่อผ่านวิธีนี้เป็นฟังก์ชั่นดิบคุณจะพบปัญหาเสมอในระหว่างการรวบรวมและประกาศไม่ตรงกัน
zoska

100

เนื่องจากคุณใช้ C ++ 11 แลมบ์ดานิพจน์เป็นคำตอบที่ดีและสะอาด

class blub {
    void test() {}
  public:
    std::thread spawn() {
      return std::thread( [this] { this->test(); } );
    }
};

เนื่องจากthis->สามารถละเว้นได้มันอาจย่อให้เหลือ:

std::thread( [this] { test(); } )

หรือเพียงแค่

std::thread( [=] { test(); } )

8
โดยทั่วไปคุณไม่ควรใช้std::moveเมื่อส่งคืนตัวแปรโลคัลตามค่า สิ่งนี้ยับยั้ง RVO ได้จริง ถ้าคุณเพียงแค่คืนค่า (โดยไม่มีการย้าย) คอมไพเลอร์อาจใช้ RVO และหากไม่ได้มาตรฐานก็บอกว่ามันต้องเรียกซีแมนทิกส์การย้าย
zmb

@zmb ด้วยข้อยกเว้นที่คุณต้องการให้โค้ดรวบรวมบน VC10 คุณต้องย้ายถ้าประเภทการส่งคืนไม่ใช่ CopyConstructable
abergmeier

6
RVO ยังสร้างรหัสได้ดีกว่าซีแมนทิกส์การย้ายและจะไม่หายไป
Jonathan Wakely

2
[=]โปรดใช้ความระมัดระวังด้วย โดยที่คุณสามารถคัดลอกวัตถุขนาดใหญ่โดยไม่ได้ตั้งใจ โดยทั่วไปก็เป็นกลิ่นรหัสที่จะใช้หรือ[&] [=]
rustyx

3
@ ทุกคนอย่าลืมว่ามันเป็นหัวข้อที่นี่ นี่หมายความว่าฟังก์ชั่นแลมบ์ดาอาจมีขอบเขตอายุการใช้งานที่ยาวนานกว่า ดังนั้นโดยใช้การจับภาพอ้างอิง ( [&]) คุณอาจแนะนำข้อบกพร่องเช่นการอ้างอิงที่ห้อยอยู่ (ตัวอย่างเช่นstd::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss

29

นี่คือตัวอย่างที่สมบูรณ์

#include <thread>
#include <iostream>

class Wrapper {
   public:
      void member1() {
          std::cout << "i am member1" << std::endl;
      }
      void member2(const char *arg1, unsigned arg2) {
          std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;
      }
      std::thread member1Thread() {
          return std::thread([=] { member1(); });
      }
      std::thread member2Thread(const char *arg1, unsigned arg2) {
          return std::thread([=] { member2(arg1, arg2); });
      }
};
int main(int argc, char **argv) {
   Wrapper *w = new Wrapper();
   std::thread tw1 = w->member1Thread();
   std::thread tw2 = w->member2Thread("hello", 100);
   tw1.join();
   tw2.join();
   return 0;
}

การคอมไพล์ด้วย g ++ สร้างผลลัพธ์ต่อไปนี้

g++ -Wall -std=c++11 hello.cc -o hello -pthread

i am member1
i am member2 and my first arg is (hello) and second arg is (100)

10
ไม่เกี่ยวข้องกับคำถาม OP แต่ทำไมคุณจัดสรร Wrapper ใน heap (และไม่ได้ยกเลิกการจัดสรร) คุณมีพื้นหลัง java / c # หรือไม่
Alessandro Teruzzi

อย่าลืมdeleteความทรงจำจากกอง :)
Slack Bot

19

@ hop5 และ @RnMss แนะนำให้ใช้ lambdas C ++ 11 แต่ถ้าคุณจัดการกับพอยน์เตอร์คุณสามารถใช้โดยตรงได้:

#include <thread>
#include <iostream>

class CFoo {
  public:
    int m_i = 0;
    void bar() {
      ++m_i;
    }
};

int main() {
  CFoo foo;
  std::thread t1(&CFoo::bar, &foo);
  t1.join();
  std::thread t2(&CFoo::bar, &foo);
  t2.join();
  std::cout << foo.m_i << std::endl;
  return 0;
}

เอาท์พุท

2

ตัวอย่างที่เขียนซ้ำจากคำตอบนี้จะเป็น:

#include <thread>
#include <iostream>

class Wrapper {
  public:
      void member1() {
          std::cout << "i am member1" << std::endl;
      }
      void member2(const char *arg1, unsigned arg2) {
          std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;
      }
      std::thread member1Thread() {
          return std::thread(&Wrapper::member1, this);
      }
      std::thread member2Thread(const char *arg1, unsigned arg2) {
          return std::thread(&Wrapper::member2, this, arg1, arg2);
      }
};

int main() {
  Wrapper *w = new Wrapper();
  std::thread tw1 = w->member1Thread();
  tw1.join();
  std::thread tw2 = w->member2Thread("hello", 100);
  tw2.join();
  return 0;
}

0

ผู้ใช้บางคนได้รับคำตอบแล้วและอธิบายได้ดีมาก

ฉันต้องการเพิ่มสิ่งที่เกี่ยวข้องกับเธรดอีกไม่กี่รายการ

  1. วิธีการทำงานกับ functor และเธรด โปรดดูตัวอย่างด้านล่าง

  2. เธรดจะทำสำเนาวัตถุของตัวเองในขณะที่ผ่านวัตถุ

    #include<thread>
    #include<Windows.h>
    #include<iostream>
    
    using namespace std;
    
    class CB
    {
    
    public:
        CB()
        {
            cout << "this=" << this << endl;
        }
        void operator()();
    };
    
    void CB::operator()()
    {
        cout << "this=" << this << endl;
        for (int i = 0; i < 5; i++)
        {
            cout << "CB()=" << i << endl;
            Sleep(1000);
        }
    }
    
    void main()
    {
        CB obj;     // please note the address of obj.
    
        thread t(obj); // here obj will be passed by value 
                       //i.e. thread will make it own local copy of it.
                        // we can confirm it by matching the address of
                        //object printed in the constructor
                        // and address of the obj printed in the function
    
        t.join();
    }

อีกวิธีหนึ่งในการบรรลุเป้าหมายเดียวกันคือ:

void main()
{
    thread t((CB()));

    t.join();
}

แต่ถ้าคุณต้องการผ่านวัตถุโดยอ้างอิงแล้วใช้ไวยากรณ์ด้านล่าง:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.