1. エントリ パラメータとしてのオブジェクトのライフ サイクル
複数のスレッドが同じデータにアクセスする可能性があるため、メインスレッドがスレッドコンストラクターを介して子スレッドにデータを渡す必要がある場合があるため、スレッドが作成されるたびにこれらのデータのコピーを作成してリストに保存する必要があります。 :
コンストラクタの一部:
template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;
auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{
});
関数テンプレートを使用するため、データの型を制限する必要はありません。_Invoker_proc を使用してリストをポイントします。
例として、関数名、整数、浮動小数点型、文字列型、およびカスタム オブジェクトをパラメーターとして使用して、スレッドを構築します。
class Para
{
public:
Para() {
cout << "Create Para" << endl; }
Para(const Para& p) {
cout << "Copy Para" << endl; }//参数做复制时对象进行了拷贝构造函数
~Para() {
cout << "Drop Para" << endl; }
string name;
};
void ThreadMain(int p1, float p2, string str, Para p4)
{
this_thread::sleep_for(100ms);//sleep100毫秒后已经运行过了AAA,f1等参数已经被释放,但由于参数做了复制,因此没问题
cout << "ThreadMain " << p1 << " " << p2 << " " << str <<" "<<p4.name<< endl;
}
int main(int argc, char* argv[])
{
thread th;
{
float f1 = 12.1f;
Para p;//第一次Create
p.name = "test Para class";
//所有的参数做复制,第一次Copy(),拷贝在一个链表中,然后再把这个链表的值传给进程的句柄,回调函数的入口参数第二次Copy
th = thread(ThreadMain, 101, f1, "test string para",p);
}//AAA
th.join();
return 0;
}
Para p; Prints Create Para.
スレッドのコンストラクタ constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{}); 最初の Copy Para を出力します。
_Invoker_proc が指すデータがスレッド ラップ関数に渡されると、Copy Para が 2 回出力されます。
次に、ポインタと参照がスレッド関数のパラメータとして使用されます
01 の学習では、データをコピーするときであり、問題は発生しません. ポインターまたは参照が渡された場合、2 つの問題があります: 01 複数のスレッドが同じ空間にアクセスします, 02 メインスレッドが抽出する可能性があり
ますこのスペースの
サブスレッドを解放します
サブスレッド:
void ThreadMain(int p1, float p2, string str, Para p4)
{
this_thread::sleep_for(100ms);
cout << "ThreadMain " << p1 << " " << p2 << " " << str <<" "<<p4.name<< endl;
}
void ThreadMainPtr(Para* p)
{
this_thread::sleep_for(100ms);
cout << "ThreadMainPtr name = " << p->name << endl;
}
void ThreadMainRef(Para& p)
{
this_thread::sleep_for(100ms);
cout << "ThreadMainPtr name = " << p.name << endl;
}
子スレッドが最初に終了しないようにするために、sleep_for(100ms);
メインスレッド:
int main(int argc, char* argv[])
{
{
//01
Para p;
p.name = "test ref";
thread th(ThreadMainRef, ref(p));
th.join();
}
getchar();
//02
{
Para p;
p.name = "test ThreadMainPtr name";
thread th(ThreadMainPtr, &p); //错误 ,线程访问的p空间会提前释放
th.detach();
}
getchar();
//03
{
Para p;
p.name = "test ThreadMainPtr name";
thread th(ThreadMainPtr, &p);
th.join();
}
getchar();
//04
thread th;
{
float f1 = 12.1f;
Para p;
p.name = "test Para class";
//所有的参数做复制
th = thread(ThreadMain, 101, f1, "test string para",p);
}
th.join();
return 0;
}
スレッド th(ThreadMainRef, ref§);
ref() メソッドの戻り値は reference_wrapper 型であることに注意してください.このクラスのソース コードは、おそらくポインターを維持し、演算子をオーバーロードすることを意味します.
ref() は、そうでなければ認識される値の型をラッパー型の reference_wrapper に置き換えることができ、reference_wrapper は参照される値の参照型に暗黙的に変換できます。
std::ref は、参照によって渡される値をラップするために使用されます。
スレッド メソッドは関数テンプレートを使用します. 参照を渡す場合、外側のレイヤーは ref を使用して参照を渡す必要があります.そうしないと、コンパイル エラーが発生します.
02 では para のメンバーにアクセスできないことがわかります。これは、子スレッドがメイン スレッドから切り離されているが、そのハンドルのサイクルだけが子スレッドに渡されているにもかかわらず、para のライフ サイクルが終了しているためです。 }の終わりで
01と03ではjoin()を使っており、アクセスしたメンバはコピーされていませんが、メインスレッドはjoin()でブロックされており、メインスレッドはparaにアクセスした後に解放されるので問題ありません。
04では、paraの生成を{}に置いてもライフサイクルは{}の終わりで終わりますが、パラはスレッドのコンストラクタにコピーされるので問題なく正常にアクセスできます。
3.スレッドエントリとしてのメンバー関数
実際のアプリケーションでは、オブジェクトはスレッドとして保存されることが多く、オブジェクトのすべてのメンバーはスレッドのパラメーターとして使用でき、スレッドのエントリ関数はオブジェクトのメンバー関数です。したがって、スレッドのライフサイクルはこのオブジェクトと一致しており、管理が容易です。
メンバー関数と通常の関数の違いは、メンバー関数の最初の暗黙的なポインター パラメーターが、クラス オブジェクトのメンバーにアクセスする this ポインターであることです。
スレッドのコンストラクターがクラスで関数ポインターを使用するだけでは十分ではなく、オブジェクトの this ポインターも必要です。
th_ = std::thread(&XThread::Main, this);
スレッドを定義するときにパラメーターを渡す必要はありませんが、オブジェクトのメンバーに値を割り当てる必要があります
thread th(&MyThread::Main, &myth);
実際には:
class MyThread
{
public:
//入口线程函数
void Main()
{
cout << "MyThread Main " << name << ":" << age;
}
string name;
int age = 100;
};
int main(int argc, char* argv[])
{
MyThread myth;
myth.name = "Test name 001";
myth.age = 20;
thread th(&MyThread::Main, &myth);
th.join();
return 0;
}
オッサンには何の問題もありません!
実際にスレッドをより便利に使用するために、オブジェクトを介してスレッドを生成する基本クラスを定義し、特定のクラスを使用するときにこの基本クラスを継承することがよくあります。
基本クラス:
class XThread
{
public:
virtual void Start()
{
is_exit_ = false;
th_ = std::thread(&XThread::Main, this);
}
virtual void Stop()
{
is_exit_ = true;
Wait();
}
virtual void Wait()
{
if (th_.joinable())//线程ID号还在不在
th_.join();
}
bool is_exit() {
return is_exit_; }
private:
virtual void Main() = 0;
std::thread th_;
bool is_exit_ = false;
};
if (th_.joinable())//スレッドID番号(PID)がまだある、あるなら真と判断してth_.join()を呼び出し、メインスレッドのタイムスライスをサブスレッド
main を純粋仮想関数として定義し、派生クラスを通じてその定義を完成させます。
class TestXThread :public XThread
{
public:
void Main() override//不用在编译阶段才发现拼写错
{
cout << "TestXThread Main begin" << endl;
while (!is_exit())
{
this_thread::sleep_for(100ms);
cout << "." << flush;
}
cout << "TestXThread Main end" << endl;
}
string name;
};
コンパイル段階でスペルミスを見つけずにオーバーライドを使用する
メインスレッドは次を呼び出します。
int main(int argc, char* argv[])
{
TestXThread testth;
testth.name = "TestXThread name ";
testth.Start();
this_thread::sleep_for(3s);
testth.Stop();
testth.Wait();
getchar();
}
01 最初に新しいオブジェクトを作成し、そのメンバーに値を割り当てます. この時点で、オブジェクトとスレッドは何もしません.
02 testth.Start() を使用します; オブジェクトをスレッドにカプセル化します, スレッドエントリパラメータは派生クラスのメインであり、メイン スレッドは 3 秒間休止するため、メインは 3 秒
03 3 秒の終わりのポイントに到達し、testth.Stop() は終了するサブスレッドに通知します。 exit
04 は testth.Wait を使用します(); メインスレッドをブロックし、サブスレッドの終了を待ちます