Heim  >  Artikel  >  Backend-Entwicklung  >  Einführung in die Grundlagen der C++11-Multithread-Programmierung

Einführung in die Grundlagen der C++11-Multithread-Programmierung

无忌哥哥
无忌哥哥Original
2018-07-19 09:52:332267Durchsuche

1. Erstellen Sie einen neuen Thread in C++11

In jeder C++-Anwendung gibt es einen Standard-Hauptthread, der die Hauptfunktion in C++11 darstellt andere Threads durch Erstellen von Objekten der Klasse std::thread. Jedes std::thread-Objekt kann einem Thread zugeordnet werden, indem einfach die Header-Datei 61fe42cd48946e53c78c0e2bbfbc7b04 eingefügt wird. Sie können ein std::thread-Objekt verwenden, um einen Rückruf anzuhängen, der ausgeführt wird, wenn dieser neue Thread startet. Diese Rückrufe können Funktionszeiger, Funktionsobjekte oder Lambda-Funktionen sein.
Thread-Objekte können über std::thread thObj(e713b189830c2ff991f906dd1cce83fe) erstellt werden. Der neue Thread wird sofort nach der Erstellung des neuen Objekts gestartet und der übergebene Callback wird parallel zum gestarteten Thread ausgeführt. Darüber hinaus kann jeder Thread darauf warten, dass ein anderer Thread beendet wird, indem er die Funktion „join()“ für das Objekt dieses Threads aufruft.
Erstellen Sie einen Thread mit einem Funktionszeiger:

//main.cpp
#include <iostream>
#include <thread>
void thread_function() {    
    for (int i = 0; i < 5; i++)        
    std::cout << "thread function excuting" << std::endl;
}int main() {    
    std::thread threadObj(thread_function);    
    for (int i = 0; i < 5; i++)        
    std::cout << "Display from MainThread" << std::endl;
   threadObj.join();    
   std::cout << "Exit of Main function" << std::endl;    return 0;
}

Erstellen Sie einen Thread mit einem Funktionsobjekt:

#include <iostream>
#include <thread>
class DisplayThread {
    public:void operator ()() {        
        for (int i = 0; i < 100; i++)            
        std::cout << "Display Thread Excecuting" << std::endl;
    }
};
int main() {    
    std::thread threadObj((DisplayThread()));    
    for (int i = 0; i < 100; i++)        
    std::cout << "Display From Main Thread " << std::endl;    
    std::cout << "Waiting For Thread to complete" << std::endl;
    threadObj.join();    
    std::cout << "Exiting from Main Thread" << std::endl;    
    return 0;
}

CmakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(Thread_test)set(CMAKE_CXX_STANDARD 11)
find_package(Threads REQUIRED)
add_executable(Thread_test main.cpp)
target_link_libraries(Thread_test ${CMAKE_THREAD_LIBS_INIT})

Jedes std::thread-Objekt hat eine zugehörige ID , std::thread::get_id() – gibt die ID des entsprechenden Thread-Objekts in der Member-Funktion an;
std::this_thread::get_id() – gibt die ID des aktuellen Threads an, wenn std::thread Objekt Wenn kein zugeordneter Thread vorhanden ist, gibt get_id() das standardmäßig konstruierte std::thread::id-Objekt zurück: „Kein Thread“, std::thread::id kann auch eine ID darstellen.

2.Threads verbinden und trennen

Sobald ein Thread gestartet ist, kann ein anderer Thread warten, bis die Ausführung dieses Threads abgeschlossen ist, indem er die Funktion „join()“ für das std::thread-Objekt aufruft:

std::thread threadObj(funcPtr); 
threadObj.join();

Zum Beispiel startet der Hauptthread 10 Threads. Nach dem Start wartet die Hauptfunktion darauf, dass sie alle Threads abschließen. Nach dem Zusammenführen aller Threads kann die Hauptfunktion weiter ausgeführt werden:

#include <iostream>
#include <thread>
#include <algorithm>
class WorkerThread
{
    public:void operator()(){        
        std::cout<<"Worker Thread "<<std::this_thread::get_id()<<"is Excecuting"<<std::endl;
    }
};
    int main(){    
        std::vector<std::thread> threadList;    
        for(int i = 0; i < 10; i++){
        threadList.push_back(std::thread(WorkerThread()));
    }    
    // Now wait for all the worker thread to finish i.e.
    // Call join() function on each of the std::thread object
    std::cout<<"Wait for all the worker thread to finish"<<std::endl;    
    std::for_each(threadList.begin(), threadList.end(), std::mem_fn(&std::thread::join));    
    std::cout<<"Exiting from Main Thread"<<std::endl;    
    return 0;
}

trennen Trennen Sie den Thread vom Thread-Objekt, sodass der Thread als Hintergrund-Thread ausgeführt wird und der aktuelle Thread nicht mehr kontaktiert werden kann. Wenn die Thread-Ausführungsfunktion jedoch temporär verwendet wird Es kann zu Problemen kommen, wenn der Thread im Hintergrund ausgeführt wird und die temporären Variablen zerstört werden. Dann greift der Thread auf die zerstörten Variablen zu. Sie müssen die Funktion std::detach() aufrufen Das std::thread-Objekt:

std::thread threadObj(funcPtr)
threadObj.detach();

Nach dem Aufruf von detach() ist das std::thread-Objekt nicht mehr vorsichtig. Seien Sie vorsichtig, wenn Sie detach() und join() für ein Thread-Handle aufrufen, da es mit dem tatsächlichen verknüpft ist Thread der Ausführung.

3. Übergeben Sie Parameter an den Thread

Um Parameter an das zuordenbare Objekt oder die Funktion des Threads zu übergeben, übergeben Sie einfach Argumente an den std::thread-Konstruktor, und zwar standardmäßig alle Argumente wird in den internen Speicher des neuen Threads kopiert.
Parameter an den Thread übergeben:

#include <iostream>
#include <string>
#include <thread>
void threadCallback(int x, std::string str) {  
    std::cout << "Passed Number = " << x << std::endl;  
    std::cout << "Passed String = " << str << std::endl;
}int main() {  
    int x = 10;  
    std::string str = "Sample String";  
    std::thread threadObj(threadCallback, x, str);
  threadObj.join();  
  return 0;
}

Referenz an den Thread übergeben:

#include <iostream>
#include <thread>
void threadCallback(int const& x) {  
    int& y = const_cast<int&>(x);
  y++;  
  std::cout << "Inside Thread x = " << x << std::endl;
}int main() {  
    int x = 9;  
    std::cout << "In Main Thread : Before Thread Start x = " << x << std::endl;  
    std::thread threadObj(threadCallback, x);
  threadObj.join();  
  std::cout << "In Main Thread : After Thread Joins x = " << x << std::endl;  
  return 0;
}

Das Ausgabeergebnis ist:
Im Hauptthread: Vor Thread-Start x = 9
Innerhalb des Threads x = 10
Im Hauptthread: Nach dem Thread-Join x = 9

Prozess mit Exit-Code 0 beendet
Obwohl threadCallback Parameter als Referenzen akzeptiert, ändert es den Wert von x in nicht main und die Thread-Referenz. Außerhalb ist es unsichtbar. Dies liegt daran, dass x in der Thread-Funktion threadCallback auf einen temporären Wert verweist, der in den Stapel des neuen Threads kopiert wurde und mit std::ref geändert werden kann:

#include <iostream>
#include <thread>
void threadCallback(int const& x) {  
    int& y = const_cast<int&>(x);
  y++;  
  std::cout << "Inside Thread x = " << x << std::endl;
}int main() {  
    int x = 9;  std::cout << "In Main Thread : Before Thread Start x = " << x << std::endl;  
    std::thread threadObj(threadCallback, std::ref(x));
  threadObj.join();  
  std::cout << "In Main Thread : After Thread Joins x = " << x << std::endl;  
  return 0;
}

Das Ausgabeergebnis lautet:
In Main Thread: Vor Thread-Start x = 9
Innerhalb des Threads x = 10
Im Hauptthread: Nach dem Thread-Beitritt x = 10

Prozess abgeschlossen mit Exit-Code 0
Gibt einen Zeiger auf ein Mitglied an Funktion einer Klasse als Thread-Funktion, übergeben Sie den Zeiger an die Mitgliedsfunktion als Rückruffunktion und zeigen Sie den Zeiger auf das Objekt als zweiten Parameter:

#include <iostream>
#include <thread>
class DummyClass { 
public:
  DummyClass() { }
  DummyClass(const DummyClass& obj) { }  
  void sampleMemberfunction(int x) {    
      std::cout << "Inside sampleMemberfunction " << x << std::endl;
  }
};
    int main() {
      DummyClass dummyObj;  
      int x = 10;  
      std::thread threadObj(&DummyClass::sampleMemberfunction, &dummyObj, x);
      threadObj.join();  
      return 0;
}

4. Teilen von Daten zwischen Threads und Race Conditions

Beim Multithreading ist der Datenaustausch zwischen Programmen einfach, aber dieser Datenaustausch innerhalb eines Programms kann Probleme verursachen, darunter eine Race Condition. Wenn zwei oder mehr Threads eine Reihe von Vorgängen parallel ausführen und auf denselben Speicherort zugreifen, ändern einer oder mehrere von ihnen die Daten am Speicherort, was zu unerwarteten Ergebnissen führen kann. Race-Bedingungen sind oft schwieriger zu erkennen und zu reproduzieren, da sie nicht immer auftreten; sie treten nur dann auf, wenn die relative Reihenfolge, in der zwei oder mehr Threads Vorgänge ausführen, zu unerwarteten Ergebnissen führt.
Erstellen Sie beispielsweise 5 Threads, die ein Objekt der Klasse Wallet gemeinsam nutzen, und verwenden Sie die Memberfunktion addMoney(), um parallel 100 Yuan hinzuzufügen. Wenn also das Geld in der Brieftasche anfänglich 0 ist, sollte das Geld in der Brieftasche nach Abschluss der Wettbewerbsausführung aller Threads 500 betragen. Da jedoch alle Threads die gemeinsam genutzten Daten gleichzeitig ändern, kann es in einigen Fällen passieren, dass die Das Geld in der Brieftasche kann deutlich weniger als 500 betragen.
Der Test ist wie folgt:

#include <iostream>
#include <thread>
#include <algorithm>
class Wallet {    
    int mMoney;
    public: Wallet() : mMoney(0) { }    
    int getMoney() { return mMoney; }    
    void addMoney(int money) {        
        for (int i = 0; i < money; i++) {
            mMoney++;
        }
    }
};int testMultithreadWallet() {
    Wallet walletObject;    
    std::vector<std::thread> threads;    
    for (int i = 0; i < 5; i++) {
        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100));
    }    for (int i = 0; i < 5; i++) {
        threads.at(i).join();
    }    
    return walletObject.getMoney();
}int main() {    
        int val = 0;    
        for (int k = 0; k < 100; k++) {        
        if ((val=testMultithreadWallet()) != 500) {            
            std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;
        }
    }    
    return 0;
}

Jeder Thread erhöht parallel dieselbe Mitgliedsvariable „mMoney“, die wie eine Zeile aussieht, aber dieses „nMoney++“ wird tatsächlich in 3 Maschinenbefehle umgewandelt:
·Laden Sie die Variable „mMoney“ in Register
·Erhöhen Sie den Wert von Register
·Aktualisieren Sie die Variable „mMoney“ mit dem Wert von Register
In diesem Fall wird eine Erhöhung ignoriert, da dies der Fall ist keine Inkrement-mMoney-Variable, sondern ein anderes Register wird hinzugefügt und der Wert der „mMoney“-Variable wird überschrieben.

5.使用mutex处理竞争条件

为了处理多线程环境中的竞争条件,我们需要mutex互斥锁,在修改或读取共享数据前,需要对数据加锁,修改完成后,对数据进行解锁。在c++11的线程库中,mutexes在eed5423f7254a06cc4264105c2e6fe37头文件中,表示互斥体的类是std::mutex。
就上面的问题进行处理,Wallet类提供了在Wallet中增加money的方法,并且在不同的线程中使用相同的Wallet对象,所以我们需要对Wallet的addMoney()方法加锁。在增加Wallet中的money前加锁,并且在离开该函数前解锁,看代码:Wallet类内部维护money,并提供函数addMoney(),这个成员函数首先获取一个锁,然后给wallet对象的money增加指定的数额,最后释放锁。

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
class Wallet {    
        int mMoney;    
        std::mutex mutex;public:
    Wallet() : mMoney(0) { }    
    int getMoney() { return mMoney;}    
    void addMoney(int money) {
        mutex.lock();        
        for (int i = 0; i < money; i++) {
            mMoney++;
        }
        mutex.unlock();
    }
};int testMultithreadWallet() {
    Wallet walletObject;    
    std::vector<std::thread> threads;    
    for (int i = 0; i < 5; ++i) {
        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 1000));
    }    for (int i = 0; i < threads.size(); i++) {
        threads.at(i).join();
    }    
    return walletObject.getMoney();
}int main() {    
        int val = 0;    
        for (int k = 0; k < 1000; k++) {        
        if ((val = testMultithreadWallet()) != 5000) {            
        std::cout << "Error at count= " << k << " money in wallet" << val << std::endl;
        }
    }    
    return 0;
}

这种情况保证了钱包里的钱不会出现少于5000的情况,因为addMoney()中的互斥锁确保了只有在一个线程修改完成money后,另一个线程才能对其进行修改,但是,如果我们忘记在函数结束后对锁进行释放会怎么样?这种情况下,一个线程将退出而不释放锁,其他线程将保持等待,为了避免这种情况,我们应当使用std::lock_guard,这是一个template class,它为mutex实现RALL,它将mutex包裹在其对象内,并将附加的mutex锁定在其构造函数中,当其析构函数被调用时,它将释放互斥体。

class Wallet {  
    int mMoney;  
    std::mutex mutex; public:
  Wallet() : mMoney(0) { }  int getMoney() { return mMoney;}  
  void addMoney(int money) {    
  std::lock_guard<std::mutex> lockGuard(mutex);    
  for (int i = 0; i < mMoney; ++i) {      
  //如果在此处发生异常,lockGuadr的析构函数将会因为堆栈展开而被调用
      mMoney++;      
      //一旦函数退出,那么lockGuard对象的析构函数将被调用,在析构函数中mutex会被释放
    }

  }
};

6.条件变量

  条件变量是一种用于在2个线程之间进行信令的事件,一个线程可以等待它得到信号,其他的线程可以给它发信号。在c++11中,条件变量需要头文件87d06fb90893e2a96c75139ed482c1e2,同时,条件变量还需要一个mutex锁。
  条件变量是如何运行的:
  ·线程1调用等待条件变量,内部获取mutex互斥锁并检查是否满足条件;
  ·如果没有,则释放锁,并等待条件变量得到发出的信号(线程被阻塞),条件变量的wait()函数以原子方式提供这两个操作;
  ·另一个线程,如线程2,当满足条件时,向条件变量发信号;
  ·一旦线程1正等待其恢复的条件变量发出信号,线程1便获取互斥锁,并检查与条件变量相关关联的条件是否满足,或者是否是一个上级调用,如果多个线程正在等待,那么notify_one将只解锁一个线程;
  ·如果是一个上级调用,那么它再次调用wait()函数。
  条件变量的主要成员函数:
Wait()
它使得当前线程阻塞,直到条件变量得到信号或发生虚假唤醒;
它原子性地释放附加的mutex,阻塞当前线程,并将其添加到等待当前条件变量对象的线程列表中,当某线程在同样的条件变量上调用notify_one() 或者 notify_all(),线程将被解除阻塞;
这种行为也可能是虚假的,因此,解除阻塞后,需要再次检查条件;
一个回调函数会传给该函数,调用它来检查其是否是虚假调用,还是确实满足了真实条件;
当线程解除阻塞后,wait()函数获取mutex锁,并检查条件是否满足,如果条件不满足,则再次原子性地释放附加的mutex,阻塞当前线程,并将其添加到等待当前条件变量对象的线程列表中。
notify_one()
如果所有线程都在等待相同的条件变量对象,那么notify_one会取消阻塞其中一个等待线程。
notify_all()
如果所有线程都在等待相同的条件变量对象,那么notify_all会取消阻塞所有的等待线程。

#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;
class Application {    
    std::mutex m_mutex;    
    std::condition_variable m_condVar;    
    bool m_bDataLoaded;public:
  Application() {
        m_bDataLoaded = false;
    }    
    void loadData() {        
            //使该线程sleep 1秒
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));        
        std::cout << "Loading Data from XML" << std::endl;        //锁定数据
        std::lock_guard<std::mutex> guard(m_mutex);        //flag设为true,表明数据已加载
        m_bDataLoaded = true;        //通知条件变量
        m_condVar.notify_one();
    }    bool isDataLoaded() {        
             return m_bDataLoaded;
    }    void mainTask() {        
            std::cout << "Do some handshaking" << std::endl;        //获取锁
        std::unique_lock<std::mutex> mlock(m_mutex);        //开始等待条件变量得到信号
        //wait()将在内部释放锁,并使线程阻塞
        //一旦条件变量发出信号,则恢复线程并再次获取锁
        //然后检测条件是否满足,如果条件满足,则继续,否则再次进入wait
        m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));        
        std::cout << "Do Processing On loaded Data" << std::endl;
    }
};int main() {
    Application app;    
    std::thread thread_1(&Application::mainTask, &app);    
    std::thread thread_2(&Application::loadData, &app);
    thread_2.join();
    thread_1.join();    return 0;
}

Das obige ist der detaillierte Inhalt vonEinführung in die Grundlagen der C++11-Multithread-Programmierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn