C++中的並發與多線程


本文整理自:https://www.cnblogs.com/lidabo/p/7852033.html

1. C++中的並發與多線程

C++標准並沒有提供對多進程並發的原生支持,所以C++的多進程並發要靠其他API——這需要依賴相關平台。
C++11 標准提供了一個新的線程庫,內容包括了管理線程、保護共享數據、線程間的同步操作、低級原子操作等各種類。標准極大地提高了程序的可移植性,以前的多線程依賴於具體的平台,而現在有了統一的接口進行實現。

C++11 新標准中引入了幾個頭文件來支持多線程編程:

  • < thread > :包含std::thread類以及std::this_thread命名空間。管理線程的函數和類在 中聲明.
  • < atomic > :包含std::atomic和std::atomic_flag類,以及一套C風格的原子類型和與C兼容的原子操作的函數。
  • < mutex > :包含了與互斥量相關的類以及其他類型和函數
  • < future > :包含兩個Provider類(std::promise和std::package_task)和兩個Future類(std::future和std::shared_future)以及相關的類型和函數。
  • < condition_variable > :包含與條件變量相關的類,包括std::condition_variable和std::condition_variable_any。

知識鏈接:

C++11 並發之std::atomic
 
本文概要:
1、成員類型和成員函數。
2、std::thread 構造函數。
3、異步。
4、多線程傳遞參數。
5、join、detach。
6、獲取CPU核心個數。
7、CPP原子變量與線程安全。
8、lambda與多線程。
9、時間等待相關問題。
10、線程功能拓展。
11、多線程可變參數。
12、線程交換。
13、線程移動。
std::thread 在 #include<thread> 頭文件中聲明,因此使用 std::thread 時需要包含 #include<thread> 頭文件。
1、成員類型和成員函數。
成員類型:

成員函數:

Non-member overloads:

2、std::thread 構造函數。
如下表:
(1).默認構造函數,創建一個空的 thread 執行對象。
(2).初始化構造函數,創建一個 thread 對象,該 thread 對象可被 joinable,新產生的線程會調用 fn 函數,該函數的參數由 args 給出。
(3).拷貝構造函數(被禁用),意味着 thread 不可被拷貝構造。
(4).move 構造函數,move 構造函數,調用成功之后 x 不代表任何 thread 執行對象。
注意:可被 joinable 的 thread 對象必須在他們銷毀之前被主線程 join 或者將其設置為 detached。
std::thread 各種構造函數例子如下:
 
#include<iostream>  
#include<thread>  
#include<chrono>  
using namespace std;  
void fun1(int n)  //初始化構造函數  
{  
    cout << "Thread " << n << " executing\n";  
    n += 10;  
    this_thread::sleep_for(chrono::milliseconds(10));  
}  
void fun2(int & n) //拷貝構造函數  
{  
    cout << "Thread " << n << " executing\n";  
    n += 20;  
    this_thread::sleep_for(chrono::milliseconds(10));  
}  
int main()  
{  
    int n = 0;  
    thread t1;               //t1不是一個thread  
    thread t2(fun1, n + 1);  //按照值傳遞  
    t2.join();  
    cout << "n=" << n << '\n';  
    n = 10;  
    thread t3(fun2, ref(n)); //引用  
    thread t4(move(t3));     //t4執行t3,t3不是thread  
    t4.join();  
    cout << "n=" << n << '\n';  
    return 0;  
}  

輸出結果:

Thread 1 executing
n=0
Thread 10 executing
n=30

3、異步。
例如:
 1 #include<iostream>  
 2 #include<thread>  
 3 using namespace std;  
 4 void show()  
 5 {  
 6     cout << "hello cplusplus!" << endl;  
 7 }  
 8 int main()  
 9 {  
10     //棧上  
11     thread t1(show);   //根據函數初始化執行  
12     thread t2(show);  
13     thread t3(show);  
14     //線程數組  
15     thread th[3]{thread(show), thread(show), thread(show)};   
16     //堆上  
17     thread *pt1(new thread(show));  
18     thread *pt2(new thread(show));  
19     thread *pt3(new thread(show));  
20     //線程指針數組  
21     thread *pth(new thread[3]{thread(show), thread(show), thread(show)});  
22     return 0;  
23 }
4、多線程傳遞參數。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
void show(const char *str, const int id)  
{  
    cout << "線程 " << id + 1 << "" << str << endl;  
}  
int main()  
{  
    thread t1(show, "hello cplusplus!", 0);  
    thread t2(show, "你好,C++!", 1);  
    thread t3(show, "hello!", 2);  
    return 0;  
}  
發現,線程 t1、t2、t3 都執行成功!
 
5、join、detach。
join例子如下:
 1 #include<iostream>  
 2 #include<thread>  
 3 #include<array>  
 4 using namespace std;  
 5 void show()  
 6 {  
 7     cout << "hello cplusplus!" << endl;  
 8 }  
 9 int main()  
10 {  
11     array<thread, 3>  threads = { thread(show), thread(show), thread(show) };  
12     for (int i = 0; i < 3; i++)  
13     {  
14         cout << threads[i].joinable() << endl;//判斷線程是否可以join  
15         threads[i].join();//主線程等待當前線程執行完成再退出  
16     }  
17     return 0;  
18 } 

輸出結果:

hello cplusplus!
hello cplusplus!
hello cplusplus!
1
1
1

總結:
join 是讓當前主線程等待所有的子線程執行完,才能退出。
detach例子如下:
 1 #include<iostream>  
 2 #include<thread>  
 3 using namespace std;  
 4 void show()  
 5 {  
 6     cout << "hello cplusplus!" << endl;  
 7 }  
 8 int main()  
 9 {  
10     thread th(show);  
11     //th.join();   
12     th.detach();//脫離主線程的綁定,主線程掛了,子線程不報錯,子線程執行完自動退出。  
13     //detach以后,子線程會成為孤兒線程,線程之間將無法通信。  
14     cout << th.joinable() << endl;  
15     return 0;  
16 }  

輸出結果:

hello cplusplus!0

結論:
線程 detach 脫離主線程的綁定,主線程掛了,子線程不報錯,子線程執行完自動退出。
線程 detach以后,子線程會成為孤兒線程,線程之間將無法通信。
 
6、獲取CPU核心個數。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
int main()  
{  
    auto n = thread::hardware_concurrency();//獲取cpu核心個數  
    cout << n << endl;  
    return 0;  
}  
結論:
通過  thread::hardware_concurrency() 獲取 CPU 核心的個數。
 
7、CPP原子變量與線程安全。
問題例如:
#include<iostream>  
#include<thread>  
using namespace std;  
const int N = 100000000;  
int num = 0;  
void run()  
{  
    for (int i = 0; i < N; i++)  
    {  
        num++;  
    }  
}  
int main()  
{  
    clock_t start = clock();  
    thread t1(run);  
    thread t2(run);  
    t1.join();  
    t2.join();  
    clock_t end = clock();  
    cout << "num=" << num << ",用時 " << end - start << " ms" << endl;  
    return 0;  
}  
從上述代碼執行的結果,發現結果並不是我們預計的200000000,這是由於線程之間發生沖突,從而導致結果不正確。
為了解決此問題,有以下方法:
(1)互斥量。
例如:
#include<iostream>  
#include<thread>  
#include<mutex>  
using namespace std;  
const int N = 100000000;  
int num(0);  
mutex m;  
void run()  
{  
    for (int i = 0; i < N; i++)  
    {  
        m.lock();  
        num++;  
        m.unlock();  
    }  
}  
int main()  
{  
    clock_t start = clock();  
    thread t1(run);  
    thread t2(run);  
    t1.join();  
    t2.join();  
    clock_t end = clock();  
    cout << "num=" << num << ",用時 " << end - start << " ms" << endl;  
    return 0;  
}  
不難發現,通過互斥量后運算結果正確,但是計算速度很慢,原因主要是互斥量加解鎖需要時間。
互斥量詳細內容 請參考 C++11 並發之std::mutex
(2)原子變量。
例如:
#include<iostream>  
#include<thread>  
#include<atomic>  
using namespace std;  
const int N = 100000000;  
atomic_int num{ 0 };//不會發生線程沖突,線程安全  
void run()  
{  
    for (int i = 0; i < N; i++)  
    {  
        num++;  
    }  
}  
int main()  
{  
    clock_t start = clock();  
    thread t1(run);  
    thread t2(run);  
    t1.join();  
    t2.join();  
    clock_t end = clock();  
    cout << "num=" << num << ",用時 " << end - start << " ms" << endl;  
    return 0;  
}  
不難發現,通過原子變量后運算結果正確,計算速度一般。
原子變量詳細內容 請參考C++11 並發之std::atomic。
(3)加入 join 。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
const int N = 100000000;  
int num = 0;  
void run()  
{  
    for (int i = 0; i < N; i++)  
    {  
        num++;  
    }  
}  
int main()  
{  
    clock_t start = clock();  
    thread t1(run);  
    t1.join();  
    thread t2(run);  
    t2.join();  
    clock_t end = clock();  
    cout << "num=" << num << ",用時 " << end - start << " ms" << endl;  
    return 0;  
}  

輸出結果:

num=200000000,用時 431 ms

不難發現,通過原子變量后運算結果正確,計算速度也很理想。
 
8、lambda與多線程。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
int main()  
{  
    auto fun = [](const char *str) {cout << str << endl; };  
    thread t1(fun, "hello world!");  
    thread t2(fun, "hello beijing!");  
    return 0;  
}  
9、時間等待相關問題。
例如:
#include<iostream>  
#include<thread>  
#include<chrono>  
using namespace std;  
int main()  
{  
    thread th1([]()  
    {  
        //讓線程等待3秒  
        this_thread::sleep_for(chrono::seconds(3));  
        //讓cpu執行其他空閑的線程  
        this_thread::yield();  
        //線程id  
        cout << this_thread::get_id() << endl;  
    });  
    return 0;  
}
10、線程功能拓展。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
class MyThread :public thread   //繼承thread  
{  
public:  
    //子類MyThread()繼承thread()的構造函數  
    MyThread() : thread()  
    {  
    }  
    //MyThread()初始化構造函數  
    template<typename T, typename...Args>  
    MyThread(T&&func, Args&&...args) : thread(forward<T>(func), forward<Args>(args)...)  
    {  
    }  
    void showcmd(const char *str)  //運行system  
    {  
        system(str);  
    }  
};  
int main()  
{  
    MyThread th1([]()  
    {  
        cout << "hello" << endl;  
    });  
    th1.showcmd("calc"); //運行calc  
    //lambda  
    MyThread th2([](const char * str)  
    {  
        cout << "hello" << str << endl;  
    }, " this is MyThread");  
    th2.showcmd("notepad");//運行notepad  
    return 0;  
}  
11、多線程可變參數。
例如:
#include<iostream>  
#include<thread>  
#include<cstdarg>  
using namespace std;  
int show(const char *fun, ...)  
{  
    va_list ap;//指針  
    va_start(ap, fun);//開始  
    vprintf(fun, ap);//調用  
    va_end(ap);  
    return 0;  
}  
int main()  
{  
    thread t1(show, "%s    %d    %c    %f", "hello world!", 100, 'A', 3.14159);  
    return 0;  
}  
12、線程交換。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
int main()  
{  
    thread t1([]()  
    {  
        cout << "thread1" << endl;  
    });  
    thread t2([]()  
    {  
        cout << "thread2" << endl;  
    });  
    cout << "thread1' id is " << t1.get_id() << endl;  
    cout << "thread2' id is " << t2.get_id() << endl;  
      
    cout << "swap after:" << endl;  
    swap(t1, t2);//線程交換  
    cout << "thread1' id is " << t1.get_id() << endl;  
    cout << "thread2' id is " << t2.get_id() << endl;  
    return 0;  
}  
兩個線程通過 swap 進行交換。
 
13、線程移動。
例如:
#include<iostream>  
#include<thread>  
using namespace std;  
int main()  
{  
    thread t1([]()  
    {  
        cout << "thread1" << endl;  
    });  
    cout << "thread1' id is " << t1.get_id() << endl;  
    thread t2 = move(t1);;  
    cout << "thread2' id is " << t2.get_id() << endl;  
    return 0;  
}  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM