第4章C ++マルチスレッドシステムプログラミングの要点

C ++マルチスレッドシステムプログラミングの要点

 

マルチスレッドシステムプログラミングの学習は、考え方の2つの変化に直面する必要があります。

1.現在のスレッドはいつでも切り替えることができます

2.マルチスレッドのイベントのシーケンスはグローバル優先度がなくなります

スレッドを元に戻して次のステートメントの実行を続行する場合、グローバルデータが他のスレッドによって変更されている可能性があります。たとえば、ポインタpがロックされていない場合、if(p && p-> next){/ ** /}はセグメンテーション違反を引き起こす可能性があります。これは、論理ANDの前の分岐がtrueと評価された時点で、pが設定される可能性があるためです。 NULLに設定するか、他のスレッドによって解放された場合、後者のブランチは不正なアドレスにアクセスしました

シングルCPUシステムでは、理論的には、CPUによって実行される命令の順序を通じて、マルチスレッドの実際のインターリーブ操作を推測できます。マルチコアシステムでは、複数のスレッドが並行して実行され、各イベントに番号を付けるためのグローバルクロックさえありません。適切な同期がないと、複数のCPUで実行されている複数のスレッドのイベントのシーケンスは予測できません。適切な同期が導入されると、イベントにシーケンスが作成されます。

マルチスレッドの正確さは、どのスレッドの実行速度にも依存せず、スリープ状態を待機してイベントが発生したかどうかを判断することはできませんが、現在のスレッドが他のスレッドの実行結果を確認できるように同期する必要があります。スレッドの実行速度がどれほど速くても遅くても、プログラムは正常に実行できるはずです。

 

このデモと例を見てみましょう

bool running = false;

void threadFunc()
{
    while(running)
    {
        //get task from queue
    }
}

void start()
{
    muduo::thread t(threadFunc);
    t.start();
    running = true;
}

システムの負荷が高い場合、このコードは実行の割り当てを遅らせ、システムを直接終了させます。正しい方法は、pthread_createの前に実行を開始することです。

 

4.1基本的なスレッドプリミティブ

POSIXスレッドには110を超える機能があり、実際に一般的に使用されているのはごくわずかです。

11の基本機能は次のとおりです。

2:スレッドの作成と終了の待機。4:ミューテックスのロックとロック解除の作成と破棄5:通知ブロードキャストを待機している条件変数の作成と破棄

マルチスレッドタスクは、スレッドのミューテックスと条件を使用して簡単に完了できます。

必要に応じて、いくつかの機能を使用できます。

1.pthread_onceはmuduo :: Singletonをカプセル化します。実際、グローバル変数を直接使用することをお勧めします。

2. pthread_key *はmuduo :: ThreadLocalとしてパッケージ化されています。__threadに置き換えることを検討してください。

 

これを読んだ後、pthread_keyとは何ですか?これは「Unixネットワークプログラミング」で記録されたことを覚えています。ここでデータpthread_key_createを思い出し、Unixネットワークプログラミングの第26章26.5スレッド固有のデータをもう一度振り返ります。

静的変数を持つ非再入可能関数を複数のスレッドに持ち込むことは非常に危険です。この静的変数は各スレッドの値を保存できません。

スレッド固有のデータを使用します。この方法は単純ではありません。プログラム呼び出しシーケンスを変更する必要がなく、関数内のコードを変更するだけでよいものがいくつかあります。

スレッド固有のデータを使用することは、既存の関数をスレッドセーフな関数にするための効果的な方法です。

さまざまなシステム要件が、限られた数のスレッド固有のデータをサポートします。posixでは、この制限が128以上である必要があります。

pthread_create_keyは、使用しなくなったスレッド固有のデータのキーを作成します

キーに加えて、デストラクタポインタも提供されます。

私は本の中でデモを実行しました

#include <vector>
#include <string>
#include <assert.h>
#include <iostream>
#include <zconf.h>
#include <fcntl.h>

static pthread_key_t r1_key;
static pthread_once_t r1_once = PTHREAD_ONCE_INIT;
#define MAXLINE 1024

typedef struct{
    int r1_cnt;
    char *r1_bufptr;
    char r1_buf[MAXLINE];
}Rline;

static void readline_destructor(void* ptr)
{
    free(ptr);
}

static void readline_once(void)
{
    pthread_key_create(&r1_key,readline_destructor);
}

static ssize_t my_read(Rline *tsd,int fd,char* ptr)
{
    if(tsd->r1_cnt <= 0)
    {
        again:
        if((tsd->r1_cnt = read(fd,tsd->r1_buf,MAXLINE)) < 0)
        {
            if(errno == EINTR)
            {
                goto again;
            }
            return (-1);
        }else if(tsd->r1_cnt == 0)
        {
            return 0;
        }
        tsd->r1_bufptr = tsd->r1_buf;
    }

    tsd->r1_cnt--;
    *ptr = *tsd->r1_bufptr++;
    return(1);
}

size_t readline(int fd,void *vptr,size_t maxlen)
{
    ssize_t n,rc;

    char c, *ptr;

    void *tsd;

    pthread_once(&r1_once,readline_once);

    if((tsd = pthread_getspecific(r1_key)) == nullptr)
    {
        tsd = calloc(1,sizeof(Rline));
        pthread_setspecific(r1_key,tsd);
    }

    ptr = (char*)vptr;

    for(n=1;n<maxlen;n++)
    {
        if((rc = my_read((Rline*)tsd, fd, &c)) == 1)
        {
            *ptr++ = c;

            if(c == '\n')
            {
                break;
            }
        }else if(rc == 0)
        {
            *ptr = 0;
            return (n-1);
        }else{
            return -1;
        }
    }

    *ptr = 0;
    return n;
}
int main()
{
    int fd = open("/home/zhanglei/ourc/test/demoParser.y",O_RDWR);
    if(fd <0)
    {
        return -1;
    }
    char buf[BUFSIZ];
    int res = readline(fd,buf,BUFSIZ);
    if(res <0)
    {
        return -1;
    }
    printf("%d\n",res);
    printf("%s\n",buf);
}

デストラクタ:

デストラクタは、アチーブメントによって割り当てられたメモリ領域のみを解放します

1回限りの機能:

私たちの1回限りの関数はpthread_onceによって一度呼び出され、彼はreadlineで使用されるキーを作成するだけです。

Rline構造には、図3-18で静的として宣言されているため、前述の問題を引き起こす3つの変数が含まれています。readlineを呼び出す各スレッドは、readlineによってRline構造体を動的に割り当て、デストラクタによって解放します。

my_read関数

この関数の最初のパラメーターは、スレッドによって事前に割り当てられたRline構造体へのポインターになりました。

スレッド固有のデータを割り当てる

最初にpthread_onceを呼び出すので、readlinkを呼び出すプロセスの最初のスレッドは、pthread_onceを呼び出すことによってスレッドの特定のキー値を作成します。

特定のデータポインタを取得する

pthread_getspecificは、特定のスレッドのRline構造体へのポインターを返します。ただし、このスレッドが初めてreadlineを呼び出す場合は、nullポインターを返します。この場合、Rline構造体にスペースを割り当て、callocがr1_cntメンバーを0に初期化します。次に、pthread_setspecificを呼び出して、このスレッドのこのポインターを格納します。次回readlineが呼び出されると、pthread_getspecificは保存されたばかりのポインターを返します。

総括する

ここを読んだ後、基本的な使い方を学びました。pthread_onceはスレッド固有のキーを初期化し、特定のキーに従ってスレッド固有のデータを取得します。そうでない場合は、リセットします。

muduoのコード、スレッド固有のデータを練習に適用する方法を検討しています

注意点をいくつか挙げてください。上記のコードから、一部のバージョンでは、プロセスの1つに特定のデータの量が限られていることがわかります。たとえば、一部のposixには128しかありません。本のThreadLocalデザインは非常に単純です。コードを見てください。

 

namespace muduo
{

template<typename T>
class ThreadLocal : noncopyable
{
 public:
  ThreadLocal()
  {
    MCHECK(pthread_key_create(&pkey_, &ThreadLocal::destructor));
  }

  ~ThreadLocal()
  {
    MCHECK(pthread_key_delete(pkey_));
  }

  T& value()
  {
    T* perThreadValue = static_cast<T*>(pthread_getspecific(pkey_));
    if (!perThreadValue)
    {
      T* newObj = new T();
      MCHECK(pthread_setspecific(pkey_, newObj));
      perThreadValue = newObj;
    }
    return *perThreadValue;
  }

 private:

  static void destructor(void *x)
  {
    T* obj = static_cast<T*>(x);
    typedef char T_must_be_complete_type[sizeof(T) == 0 ? -1 : 1];
    T_must_be_complete_type dummy; (void) dummy;
    delete obj;
  }

 private:
  pthread_key_t pkey_;
};

}  // namespace muduo

 コンストラクターで、pthread_create_keyを呼び出してキーを作成し、デストラクタ内のキーを破棄するために使用されるデストラクタをバインドし、pthread_key_deleteを呼び出します。value関数を使用して値を取得し、デストラクタを使用してメモリを解放します。 。

 

以下の使用はお勧めしません。

pthread_rwlock、読み取りおよび書き込みロックは注意して使用する必要があります

sem_ *セマフォシリーズ

pthread_cancelとpthread_killの存在は、プログラム設計に問題があることを意味します

各ロックのパフォーマンスは<< unixネットワークプログラミング第2巻>>で比較されるため、このステートメントを強くお勧めします。メモリを追加する場合、ミューテックスの効率が最も高くなります。C ++マルチスレッドプログラミングの難しさは、ライブラリ関数とシステムコールの関係を理解することにあります。

 

4.2 c \ c ++のセキュリティ

マルチスレッドの出現は、次のような従来のプログラミングに影響を与えました。

1.異なるスレッドが異なるシステムライブラリ関数を実行する可能性があるため、errnoはグローバル変数ではなくなりました

2. malloc \ free、printfとfread、fseekなど、一部の純粋関数は影響を受けません。

3.静的変数を使用する一部の関数は影響を受けません。たとえば、asctime_r、ctime_r、gmtime_r、stderror_r、stock_rを使用できます。

4.従来のフォークモデルはマルチスレッドにはもはや適していません

4.3LinuxのスレッドID

POSIXは、現在のプロセス識別子を返すpthread_self関数を提供し、そのタイプはpthread_tです。pthread_tは必ずしも数値型である必要はなく、構造体である場合もあるため、pthreadsは、2つのスレッド識別子が等しいかどうかを比較するためのpthread_equal関数を具体的に提供します。

しかし、これは次のようないくつかの問題を引き起こします。

1.正確なタイプが不明なため、pthread_tを出力できません。彼のスレッドIDを表現できません

2. pthread_tのサイズを比較したり、その値を計算したりすることはできないため、連想コンテナのキーとして使用することはできません。

3.スレッドが存在しないことを示すために不正なpthread_tを定義することはできません

4. pthread_tはプロセスで意味があり、オペレーティングシステムのスケジューリングとの効果的な関連付けを確立できません。

さらに、glibcのpthreadは実際にはpthread_tを構造体ポインタとして使用しており、このメモリブロックは簡単に再利用できます。

したがって、pthread_tはスレッド識別子としては適していません

LinuxではスレッドのIDとしてgettidシステムコールを使用することをお勧めします

1.返されるタイプは、ログに簡単に出力できるようにpid_tです。

2.最近のシステムでは、カーネルによってスケジュールされた特定のタスクIDを表すため、/ proc / tidまたは/ proc / pid / task / tidで簡単に見つけることができます。

3.それはいつでもグローバルに一意であり、Linuxはサイクルを増やすことによって新しいpidを割り当てるため、短時間で複数のスレッドを開始すると、異なるスレッドIDがあると感じます。

オペレーティングシステムの最初のプロセス初期化のpidは一意であるため、4.0は不正な値です。もちろん、ubuntuとシステムはsystemdです。

glibcはこの関数を提供していません。自分で作成する必要があります。muduoの実装方法のコアコードを見てみましょう。

#include <sys/syscall.h>
::syscall(SYS_gettid)

もちろん、muduoは効率を改善するためにキャッシュされています

4.4スレッドの作成と破棄

スレッドの作成と破棄は基本的な要素です。スレッドの作成は破棄よりもはるかに簡単です。次の原則に従うだけで済みます。

1.ライブラリは、事前の通知なしに独自のバックグラウンドスレッドを作成しないでください

2.同じ方法でスレッドを作成してみてください

3.メイン機能に入る前にスレッドを開始しないでください

4.プログラムでのスレッドの作成は、初期化フェーズで完了するのが最適です。

これらの点について個別に話しましょう。

プロセスが作成できる同時スレッドの数は、アドレススペースのサイズとカーネルのパラメーターによって制限され、マシンが並列化できるスレッドの数は、CPUの数によって制限されます。したがって、スレッド数を設計するとき、特にCPUの数に応じてスレッド数を設定するときは慎重に設計し、重要なタスクに十分なコンピューターリソースを残す必要があります。

もう1つの理由は、スレッドに複数のスレッドがある場合、フォークが問題を引き起こさないことを確認するのが難しいことです。

それで、私がプログラムを書いたとき、それはこのようでした。フォークは以前にスレッドを作成するためにpthread_createを呼び出しませんでした。

理想的には、プログラムのスレッドは同じクラスで作成されるため、プログラムの起動段階と破棄段階で統一された簿記作業を簡単に行うことができます。たとえば、muduo :: CurrentThread :: tid()を呼び出してスレッドIDをキャッシュし、スレッドIDを取得した後にカーネルに閉じ込められないようにします。また、現在のプロセスでアクティブなスレッドの数、合計で作成されたスレッドの数、および各スレッドの目的を数えることもできます。クラスごとにスレッドに名前を付けることも、シングルトンを使用してthreadManagerを作成し、現在アクティブなスレッドを管理してデバッグを容易にすることもできます。

ただし、これが常に可能であるとは限りません。サードパーティのライブラリは独自のワイルドスレッドを開始するため、スレッドIDがキャッシュされて直接返されると想定するのではなく、スレッドIDが毎回有効かどうかを確認する必要があります。ライブラリが非同期コールバックを提供する場合は、ユーザーが提供する非同期コールバック関数を呼び出すスレッドを必ず説明して、時間のかかる操作を実行できるかどうか、および他のタスクの実行をブロックするかどうかをユーザーが認識できるようにしてください。

グローバルオブジェクトの安全な構築に影響を与えるため、メイン関数の前にスレッドを開始しないでください。C++は、メイン関数に入る前にグローバル初期化構築を完了していることがわかっています。同時に、コンパイルされた各オブジェクト間の構築の順序は不確実です。いずれにせよ、これらのグローバルオブジェクトの構築は整然としており、スレッドセーフの問題を考慮せずに、すべてメイン関数で順番に完了します。ただし、グローバルオブジェクトがスレッドを使用する場合、グローバルオブジェクトの初期化の前提が破られるため、危険です。スレッドが初期化されていないグローバル変数にアクセスする場合、この種のあいまいなエラーを見つけるのは非常に困難です。ライブラリがスレッドを作成したい場合は、main関数に入った後に作成する必要があります

作成されるスレッドの数はCPUに関連しています。リンクのスレッドは作成しないでください。スレッドの作成は初期化フェーズで最適であるため、頻繁にスレッドを作成および破棄するコストよりも約1/10低くなります。

スレッドを破棄する一元化された方法があります。

1.スレッドのメイン関数から自然死が戻り、スレッドは正常に終了します

2.異常死のメイン関数が例外をスローするか、セグメンテーション違反シグナルをトリガーします

3.自殺pthread_exit

4. Killingはpthread_cancelを呼び出して、スレッドを強制的に終了させます

 

スレッドが正常に終了する唯一の方法は自然死です。外部からの終了と終了の考えは間違っています。pthread_cancelはスレッドにリソースをクリーンアップする機会を与えません。すでに保持されているロックを解除する機会もありません。

時間がかかるIOタスクの終了を本当に検討したいが、特定のグローバル変数を定期的にチェックしたくない場合は、コードの一部を新しいプロセスにフォークすることを検討できます。キル(2)プロセスはプロセス内のスレッドを強制終了するよりも優れています。スレッドははるかに安全であり、フォークプロセスとこのプロセス間の通信では、パイプ、ソケットペア、またはtcpを考慮することができます。

この本の重要な原則は、オブジェクトのライフサイクルは一般にスレッドのライフサイクルよりも長いということです。

総括する:

这一段内容说的是尽量不要从外部杀死线程,最好做到线程的自然死亡,线程的创建要在main之后,还有的时候我们要考虑第三方库的野生线程造成的安全问题。
一个重要的原则就是线程的生命周期必须要短于线程的生命周期。

4.4.2 exit(3)はスレッドセーフではありません 

C ++のexit(3)の機能は、終了するだけでなく、構築された関数のグローバルオブジェクトと静的オブジェクトを分解することでもあります。これは潜在的なデッドロックの可能性です。次の例を検討してください

#include <vector>
#include <string>
#include <assert.h>
#include <iostream>
#include <zconf.h>
#include <fcntl.h>
#include <syscall.h>

class noncopyable{
protected:
    noncopyable() = default;
    ~noncopyable() = default;

private:
    noncopyable(const noncopyable&) = delete;
    const noncopyable& operator=( const noncopyable& ) = delete;
};


class MutexLock :public noncopyable{
public:
    MutexLock()
    {
        pthread_mutexattr_init(&mutexattr);
        pthread_mutex_init(&mutex, nullptr);
    }

    MutexLock(int type)
    {
        int res;
        pthread_mutexattr_init(&mutexattr);
        res = pthread_mutexattr_settype(&mutexattr,type);
        pthread_mutex_init(&mutex, &mutexattr);
    }

    ~MutexLock()
    {
        pthread_mutex_destroy(&mutex);
    }

    int lock()
    {
        int res = pthread_mutex_lock(&mutex);
        return res;
    }

    void unLock()
    {
        pthread_mutexattr_destroy(&mutexattr);
        pthread_mutex_unlock(&mutex);
    }

    pthread_mutex_t* getMutex()
    {
        return &mutex;
    }
private:
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutexattr;
};

class MutexLockGuard
{
public:
    MutexLockGuard(MutexLock & mutex)
            : _mutex(mutex)
    {
        _mutex.lock();
    }

    ~MutexLockGuard()
    {
        _mutex.unLock();
    }

private:
    MutexLock & _mutex;
};

void someFunctionMayCallExit()
{
    exit(1);
}

class GlobalObject
{
public:
    void doit()
    {
        MutexLockGuard lock(mutex_);
        someFunctionMayCallExit();
    }

    ~GlobalObject()
    {
        printf("GlobalObject:~GlobalObject\n");
        MutexLockGuard lock(mutex_);
        printf("GlobalObject:~GlobalObject cleaning\n");
    }

private:
    MutexLock mutex_;
};

GlobalObject g_obj;

int main()
{
    g_obj.doit();
}

この例は非常に興味深いプログラムです。このプログラムを使用すると、デッドロックが発生しました。exitに名前を付けた後、プログラムは正常に終了するはずでしたが、終了しませんでしたが、デッドロックが発生しました。

私たちはここで考えています、なぜそれはデッドロックなのですか?

doitでexitを呼び出した後、グローバルデストラクタ〜GlobalObject()がトリガーされ、ミューテックスをロックしようとしましたが、この時点でミューテックスがロックされ、デッドロックが発生しました。

プログラムをクラッシュさせる純粋仮想関数を呼び出す別の例を挙げましょう。ストラテジー基本クラスがある場合は、状況に応じて実行時にさまざまなステートレスストラテジーを使用します。戦略はステートレスであるため、派生オブジェクトは毎回新しいオブジェクトを作成しなくても共有できます。カレンダーの基本クラスとさまざまな国の休日を例にとると、ファクトリ関数は、毎回新しい派生クラスオブジェクトを作成する代わりに、グローバルオブジェクトへの参照を返します。

 

上記のプログラムは、終了時にグローバルオブジェクトを破棄し、別のスレッドがisHolidayを呼び出すとハングします。

1つのスレッドがexitを呼び出して、グローバルオブジェクトDateを破棄した場合、別のスレッドがisHolidayを呼び出すと、コアダンプが表示されます。

シーンでの終了は簡単な作業ではないことがわかります。各スレッドアクセスによって引き起こされるオブジェクト障害の問題を防ぐために、デストラクタの順序を慎重に設計する必要があります。

4.5__threadキーワードをうまく利用する 

__threadはgccの内部ストレージ機能であり、pthread_key_tよりも高速です。__threadのストレージ効率はグローバル変数と比較できます

int g_var;
__thread int t_var;

void foo()
{
    g_var = 1;
    t_var = 2;
}

__threadは、クラスタイプの変更には使用できません。PODオブジェクトの変更にのみ使用できます。本のPODオブジェクトは、

POD全称Plain Old Data。通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型。

标准布局的定义
1.所有非静态成员有相同的访问权限

2.继承树中最多只能有一个类有非静态数据成员

3.子类的第一个非静态成员不可以是基类类型

4.没有虚函数

5.没有虚基类

6.所有非静态成员都符合标准布局类型

クラスを呼び出せない重要な理由は、コンストラクターを呼び出せないためです。

 

#include <pthread.h>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <stdint.h>

class A{
public:
    int b;
    A(int data)
    {
        a = data;
    }
private:
    int a;
};

__thread class A a = 3;
int main(int argc, char const *argv[])
{
    a.b = 2;
    return 0;
}

 

以下のデモを作成して、__ threadによって変更された変数が各スレッドに独立したエンティティを持っているかどうかを確認します

 

#include <pthread.h>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <stdint.h>
#include <unistd.h>

__thread uint64_t pkey = 0;

void* run2( void* arg )
{
    pkey = 8;
    printf("run2-ptr:%p\n",&pkey);
    printf("run2:%ld\n",pkey);
    return NULL;
}

void* run1( void* arg )
{
    printf("run1-ptr:%p\n",&pkey);
    printf("run1:%ld\n",pkey);

    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t threads[2];
    pthread_create( &threads[1], NULL, run2, NULL );
    sleep(1);
    pthread_create( &threads[0], NULL, run1, NULL );
    pthread_join( threads[0], NULL );
    pthread_join( threads[1], NULL );
    return 0;
}

 

ここでは、__ threadが追加されたため、2番目のスレッドが元々出力8を使用し、結果が0になり、最初のスレッドの変更に応じて変化しなかったことがわかります。

4.6マルチスレッドとIO 

記事には、ファイルioはスレッドセーフであると書かれています。これについてはよくわかりません。ファイルioを処理するために常にpreadとpwriteを使用しました。以前にデモと実験を作成しました。複数のスレッドが同じように動作する必要があります。ソケット。ロックされています。ロックしないと問題が発生します

[email protected]:LeiZhang-Hunter/sendDemo.git

実際、この問題自体はあまり重要ではありません。読み取りと書き込みはどちらもアトミックです。次に、ファイルの内容を複数のスレッドで読み書きします。操作したい場合は、問題が発生しやすくなります。静的な状態は避けられません。ロック。タイミングも問題なので、各記述子はできるだけ1つのスレッドで操作する必要があると思います。

4.7RAIIを使用して記述子をカプセル化する 

プログラムが最初に開始されたときに誰もが知っていた3つの記述子

0 1 2標準入力標準出力標準エラー出力。posix標準では、ファイルを開くたびに記述子を現在の最小数にする必要があると規定されています。実際、マルチスレッド記述子のインターフェイスは、fpmインターフェイスを作成するのと同じです。 -スレッド化された読み取り頻繁に同じ記述子を閉じると、必然的に問題が発生します。いいねインターフェースが頻繁にいいねを付けたりキャンセルしたりするのと同じように、ロックされていない場合、このインターフェースは非常に危険であり、他の人から賞賛されやすく、さらに悪い場合はいいねを頻繁にキャンセルすると、マイナスになる可能性があります。これは本当に悪いことです。マルチスレッドのクローズと読み取りにより、記述子のシリアル番号が発生します。言うまでもなく、一方のスレッドはすでに読み取りを行っており、もう一方のスレッドはクローズしています。危険なことが起こります

C ++では、これを行うためにRAIIメソッドが採用されています。ソケットオブジェクトは記述子をラップするために使用され、クロージャはデストラクタで処理されます。ソケットがまだ生きている限り、同じ記述子を持つ他のソケットオブジェクトはありません。もちろん、ここのオブジェクトは非常に危険なnewの形をとっていません。これは、非常に安全なポインタとして使用できます。

私は本のアイデアに非常に同意します。可能な限り新しい削除を使用し、可能な限りスマートポインタを使用するようにしてください。

4.8RAIIとフォーク

 

c c ++を作成するときは、オブジェクトの構造とデストラクタが常にペアで表示されるようにします。そうしないと、メモリリークが発生しますが、forkを使用するように追加すると、この仮定が破られます。

forkの後、子プロセスはアドレス空間と空間記述子を継承するため、動的メモリとファイル記述子の管理に使用されるRAIIクラスは正常に機能します。しかし、子プロセスは継承できません

例えば:

1.親プロセス、mlock、mlockallのメモリ位置

2.親プロセスfcntl(2)のファイルロック

3.親プロセスの一部のタイマーsettimeralarm timer_create

4. man2フォークを使用して直接表示できます。詳細な手順があります

具体不会继承的内容 分别是

1)进程id

2)内存锁

3)未决信号集

4)信号量

5)文件锁

6)timer系列函数

7)父进程未完成的io

4.9マルチスレッドとフォーク

 この本では、フォークは通常、制御スレッドのクローンを作成するだけで、他のスレッドは消えることを紹介しています。つまり、同じマルチスレッドで子プロセスをフォークすることはできません。後で、デモを作成して確認します。フォークの後は、 1つのスレッド。他のスレッドは小さいため、非常に危険な状況が発生する可能性があります。他のスレッドがたまたまクリティカルセクションにあり、特定のロックを保持していたため、彼は突然死亡し、再びロックを解除する機会はありませんでした。子プロセスがミューテックスをロックしようとすると、すぐにデッドロックが発生します。

也就是说,如果主线程内有一个公共的锁,被其他线程持有了,这时候你fork之后只会保留控制线程,其他的线程占有了锁,你fork之后其他线程都没了,那
这个锁一旦被使用,那真的是灾难性的了,会造成死锁。

1. malloc、mallocのグローバル変数へのアクセスにはほとんどの場合ロックがあります(これについてはあまり明確ではありません。結局のところ、mallocのソースコードを見たことがありません)

2.new、map :: insert、snprintf ..... ..

3. pthread_signalを使用して親プロセスに通知することはできません。パイプを介してのみ、

4.printfシリーズの機能

5. man7で定義された信号は、リエントラント以外の任意の関数にすることができます

 

これまで、fork後のスレッドの変更を確認する例を作成しました

#include <pthread.h>
#include <cstdio>
#include <cstdlib>
#include <assert.h>
#include <stdint.h>
#include <unistd.h>

__thread uint64_t pkey = 0;

void* run2( void* arg )
{
    while(1)
    {
        printf("%d\n",getpid());
        sleep(2);
    }
    return NULL;
}

void* run1( void* arg )
{

    while(1)
    {
        printf("%d\n",getpid());
        sleep(2);
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    printf("parent:%d\n",getpid());
    pthread_t threads[2];
    pthread_create( &threads[1], NULL, run2, NULL );
    sleep(1);
    pthread_create( &threads[0], NULL, run1, NULL );
    pid_t pid = fork();
    if(pid > 0)
    {
        pthread_join( threads[0], NULL );
        pthread_join( threads[1], NULL );
    }else{
        printf("son:%d\n",getpid());
        while (1)
        {
            sleep(2);
        }
    }

    return 0;
}

 演算結果:

parent:31424
31424
31424
son:31436
31424
31424
31424
31424
31424
31424

本のステートメントを確認しました。制御スレッドのみが実行されており、他のスレッドは実行されていません。これが制御スレッドであることに注意してください。

4.10マルチスレッドとシグナル

複数のスレッドでシグナルを使用しないようにしてください

おすすめ

転載: blog.csdn.net/qq_32783703/article/details/106365829