Maison  >  Article  >  développement back-end  >  Introduction aux bases de la programmation multithread C++11

Introduction aux bases de la programmation multithread C++11

无忌哥哥
无忌哥哥original
2018-07-19 09:52:332308parcourir

1. Créez un nouveau thread en C++11

Dans chaque application C++, il existe un thread principal par défaut, qui est la fonction principale en c++11, nous pouvons créer. d'autres threads en créant des objets de classe std::thread. Chaque objet std::thread peut être associé à un thread en incluant simplement le fichier d'en-tête 61fe42cd48946e53c78c0e2bbfbc7b04. Vous pouvez utiliser un objet std::thread pour attacher un rappel qui sera exécuté au démarrage de ce nouveau thread. Ces rappels peuvent être des pointeurs de fonction, des objets de fonction ou des fonctions Lambda.
Les objets thread peuvent être créés via std::thread thObj(e713b189830c2ff991f906dd1cce83fe). Le nouveau thread démarrera immédiatement après la création du nouvel objet et le rappel passé sera exécuté en parallèle avec le thread démarré. De plus, n'importe quel thread peut attendre qu'un autre thread se termine en appelant la fonction join() sur l'objet de ce thread.
Utilisez des pointeurs de fonction pour créer des fils de discussion :

//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;
}

Utilisez des objets de fonction pour créer des fils de discussion :

#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})

Chaque objet std::thread Il y a un identifiant associé, std::thread::get_id() - la fonction membre donne l'identifiant de l'objet thread correspondant
std::this_thread::get_id() - donne l'identifiant du thread actuel, If l'objet std::thread n'a pas de thread associé, get_id() renverra l'objet std::thread::id construit par défaut : "pas n'importe quel thread", std::thread::id peut également représenter l'identifiant.

2. Rejoindre et détacher des threads

Une fois qu'un thread est démarré, un autre thread peut attendre que ce thread termine son exécution en appelant la fonction join() sur l'objet std::thread :

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

Par exemple, le thread principal démarre 10 threads. Après le démarrage, la fonction principale attend qu'ils terminent leur exécution, la fonction principale continue de s'exécuter :

#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;
}
<.>détacher peut séparer les threads des threads L'objet est séparé et le thread est exécuté en tant que thread d'arrière-plan, et le thread actuel ne sera pas bloqué. Cependant, après le détachement, il ne pourra plus contacter le thread. La fonction d'exécution utilise des variables temporaires, des problèmes peuvent survenir. Le thread appelle detach pour s'exécuter en arrière-plan, la variable temporaire peut avoir été détruite, puis le thread accédera à la variable détruite et vous devrez appeler std::detach(). fonction dans l'objet std::thread :

std::thread threadObj(funcPtr)
threadObj.detach();
Après avoir appelé detach(), std L'objet ::thread n'est plus associé au thread d'exécution réel, soyez prudent lorsque vous appelez detach() et join( ) sur le handle du thread.

3. Passer les paramètres au thread

Pour passer les paramètres Pour passer un objet ou une fonction associable à un thread, passez simplement les arguments au std::thread constructeur. Par défaut, tous les arguments seront copiés dans le stockage interne du nouveau thread.

Transmettre les paramètres au fil de discussion :

#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;
}
Transmettre la référence au fil de discussion :

#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;
}
Le résultat de sortie est :

Dans le fil de discussion principal : avant le début du fil de discussion x = 9
À l'intérieur du fil x = 10
Dans le fil principal : après la jointure du fil x = 9

Processus terminé avec le code de sortie 0

Même si threadCallback accepte les paramètres comme références, il ne modifie pas le valeur de x dans main , elle n'est pas visible en dehors de la référence du thread. En effet, x dans la fonction thread threadCallback fait référence à une valeur temporaire copiée dans la pile du nouveau thread, qui peut être modifiée à l'aide de std::ref :

#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;
}
Le résultat de sortie est :

Dans le fil principal : avant le début du fil x = 9
À l'intérieur du fil x = 10
Dans le fil principal : après la jointure du fil x = 10

Processus terminé avec le code de sortie 0

Spécifie une fonction membre d'une classe Le pointeur sert de fonction de thread, et le pointeur est passé à la fonction membre en tant que fonction de rappel, et le pointeur pointe vers l'objet comme deuxième paramètre :

#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. données entre les threads et conditions de concurrence

Le partage de données entre plusieurs threads est simple, mais ce partage de données dans le programme peut causer des problèmes, dont l'un est une condition de concurrence. Lorsque deux ou plusieurs threads effectuent un ensemble d'opérations en parallèle et accèdent au même emplacement mémoire, un ou plusieurs d'entre eux modifient les données dans l'emplacement mémoire, ce qui peut conduire à des résultats inattendus. Les conditions de concurrence sont souvent plus difficiles à repérer et à reproduire car elles ne se produisent pas toujours ; elles se produisent uniquement lorsque l'ordre relatif dans lequel deux ou plusieurs threads effectuent des opérations conduit à des résultats inattendus.

Par exemple, créez 5 threads qui partagent un objet de classe Wallet et utilisez la fonction membre addMoney() pour ajouter 100 yuans en parallèle. Ainsi, si initialement l'argent dans le portefeuille est de 0, alors une fois l'exécution de la compétition de tous les threads terminée, l'argent dans le portefeuille devrait être de 500. Cependant, puisque tous les threads modifient les données partagées en même temps, dans certains cas, le l'argent dans le portefeuille peut être bien inférieur à 500.
Le test est le suivant :

#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;
}
Chaque thread augmente la même variable membre "mMoney" en parallèle, ce qui semble être une ligne, mais ce "nMoney++" est en fait converti en 3 commandes machine :

· Charger la variable "mMoney" dans le registre
· Incrémenter la valeur du registre
· Mettre à jour la variable "mMoney" avec la valeur du registre
Dans ce cas, un incrément sera ignoré car Au lieu d'incrémenter la variable mMoney, un registre différent est incrémenté et la valeur de la variable « mMoney » est écrasée.

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;
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn