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