✊✊✊🌈大家好!本篇文章主要整理了部分多线程相关的内容重点😇。首先讲解了多进程和多线程并发的区别以及各自优缺点,之后讲解了Thead
线程库的基本使用。
本专栏知识点是通过<零声教育
>的音视频流媒体高级开发
课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育
🎡导航小助手🎡
- 一、多进程与多线程
- 二、Thead线程库的基本使用
- 三、小结
一、多进程与多线程
首先有一个直观的理解:
1.进程就是运行中的程序
2.线程就是进程中的进程
操作系统中可以有多个进程,一个进程中也可以有多个线程。
1.1 多线程并发
多进程并发是将一个应用程序划分为多个独立的进程(每个进程只有一个线程),这些独立的进程之间相互可以通信,共同完成任务。操作系统常常对进程提供大量的保护机制,避免出现一个进程修改其他进程的数据,因此,相对于多线程,使用多进程更容易写出相对安全的代码。但这也造成了多进程并发存在两个不足之处:
- 进程间的通信,无论是使用信号、套接字、还是文件、管道等方式,其使用要么比较复杂,要么就是速度较慢或者两者兼而有之。
- 运行多个线程,需要操作系统花费很多资源进行管理。
在多个进程并发完成一个任务时,常会出现操作同一个数据以及进程之间的相互通信,因此,多进程并发不是一个很好的选择。
1.2 多线程并发
多线程并发:同一个进程中执行多个线程。
- 优点:线程是轻量级的进程,每个线程可以独立的运行不同的指令序列,且线程不独立拥有资源,依赖于创建它的进程而存在。
同一进程中的多个线程能够很方便的进行数据共享以及通信,比进程更适用于并发操作。
不足:缺少操作系统提供的保护机制。在多线程共享数据及通信时,需程序员做更多的操作,并且还需极力避免死锁。
二、Thead线程库的基本使用
2.1 创建线程
要创建线程,我们需要一个可调用的函数或函数对象,作为线程的入口点。
在C++11中,我们可以使用函数指针、函数对象或lambda表达式
来实现。
创建线程的基本语法如下:
#include <thread>std::thread t(function_name, args...);
function_name
是线程入口点的函数或可调用对象args...
是传递给函数的参数
创建线程后,我们可以使用t.join()
等待线程完成,或者使用t.detach()
分离线程,让它在后台运行。
实例1:
#include <iostream>
#include <thread>
using namespace std;
void doit() { cout << "World!" << endl; }
int main() {
thread a([] {
cout << "Hello, " << flush;
}), b(doit);
a.join();
b.join();
return 0;
}
运行结果:
上面两次结果并不相同,这是因为多线程运行时是以异步方式执行的,与我们平时写的同步方式不同。异步方式可以同时执行多条语句,谁先执行得快,谁先执行完。
实例2:
#include <iostream>
#include <thread>
using namespace std;
void thread_1(){
cout<<"线程t"<<endl;
}
void print_message(const string& message) {
cout << message <<endl;
cout << "线程 t" << endl;
}
void increment(int& x) {
++x;
cout << "线程 t2" << endl;
}
int main() {
cout << "主线程1\n";
thread t(thread_1);// 开启线程t,调用:thread_1()
t.join();
cout << "子线程t结束\n";
string message = "Hello, world!";
thread t1(print_message, message);// 开启线程t1,调用:print_message()
t1.join();
int x = 0;
thread t2(increment, ref(x));//开启线程t1,调用:increment()
t2.join();
cout << "子线程t2结束\n";
cout << x << endl;
cout << "全部子进程结束\n";
return 0;
}
运行结果:
从上面结果,我们很明显能看出,使用t.join()
后程序需要等待进程t
结束后,才会接着进行。
注意:thread在传递参数时,是以右值传递的。
我们在传递引用的时候,需要用到std::ref和std::cref
std::ref
可以包装按引用传递的值。std::cref
可以包装按const引用传递的值。
2.2 join与detach方式
当线程启动后,一定要在和线程相关联的thread
销毁前,确定以何种方式等待线程执行结束。比如上
例中的join。
• detach
方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
• join
方式,等待启动的线程完成,才会继续往下执行。
可以使用joinable
判断是join
模式还是detach
模式。
示例1:join举例
#include <iostream>
#include <thread>
using namespace std;
void thread_1() {
while (1) {
cout<<"子线程1"<<endl;
}
}
void thread_2(int x) {
while (1) {
cout<<"子线程2"<<endl;
}
}
int main() {
thread first(thread_1);
// 开启线程,调用:thread_1()
thread second(thread_2, 100);
// 开启线程,调用:thread_2(100)
first.join(); // pauses until first finishes 这个操作完了之后才能destroyed
second.join(); // pauses until second finishes//join完了之后,才能往下执行。
while (1) {
std::cout << "主线程\n";
}
return 0;
}
线程1和线程2写的是死循环,那么在两个子线程没结束前,主线程不会执行。
示例2:detach举例
#include <iostream>
#include <thread>
using namespace std;
void thread_1() {
while (1) {
cout<<"子线程1"<<endl;
}
}
void thread_2(int x) {
while (1) {
cout<<"子线程2"<<endl;
}
}
int main() {
thread first(thread_1);
// 开启线程,调用:thread_1()
thread second(thread_2, 100);
// 开启线程,调用:thread_2(100)
first.join(); // pauses until first finishes 这个操作完了之后才能destroyed
second.join(); // pauses until second finishes//join完了之后,才能往下执行。
while (1) {
std::cout << "主线程\n";
}
return 0;
}
运行结果:
可以看出,主线程不会等待子线程1和2结束。如果主线程运行结束,程序则结束。
2.3 joinable
joinable()返回一个bool
值,判断是join模式还是detach模式。
使用方法;
if (myThread.joinable()) 1 foo.join();
三、小结
- 多进程安全但是浪费操作系统资源且进程间相互通信比较麻烦。多线程则可以很好的处理这两个问题,但是使用时需要使用更多操作确保安全。
- C++11提供了语言层面上的多线程,包含在头文件中。它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。主要讲解了Thead线程库的基本使用,包括join()、joinable()和detach(),并举了很多例子进行补充。
感谢大家阅读!
接下来还会继续更新多线程相关知识,感兴趣的可以看其他笔记!