C++ - 多线程

1. 认识多线程

传统的C++(C++11之前)中并没有引入线程这个概念,在C++11出来之前,如果我们想要在C++中实现多线程,需要借助操作系统平台提供的API,比如Linux的<pthread.h>,或者windows下的<windows.h> 。

C++11提供了语言层面上的多线程,包含在头文件<thread>中。它解决了跨平台的问题,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。C++11 新标准中引入了5个头文件来支持多线程编程,如下图所示:

 

 

1.1 并发和并行

  • 多进程并发

使用多进程并发是将一个应用程序划分为多个独立的进程(每个进程只有一个线程),这些独立的进程间可以互相通信,共同完成任务。由于操作系统对进程提供了大量的保护机制,以避免一个进程修改了另一个进程的数据,使用多进程比多线程更容易写出安全的代码。但是这也造就了多进程并发的两个缺点:

  1. 在进程间的通信,无论是使用信号、套接字,还是文件、管道等方式,其使用要么比较复杂,要么就是速度较慢或者两者兼而有之。

  2. 运行多个线程的开销很大,操作系统要分配很多的资源来对这些进程进行管理。

由于多个进程并发完成同一个任务时,不可避免的是:操作同一个数据和进程间的相互通信,上述的两个缺点也就决定了多进程的并发不是一个好的选择。

 

  • 多线程并发

多线程并发指的是在同一个进程中执行多个线程。

优点:

有操作系统相关知识的应该知道,线程是轻量级的进程,每个线程可以独立的运行不同的指令序列,但是线程不独立的拥有资源,依赖于创建它的进程而存在。也就是说,同一进程中的多个线程共享相同的地址空间,可以访问进程中的大部分数据,指针和引用可以在线程间进行传递。这样,同一进程内的多个线程能够很方便的进行数据共享以及通信,也就比进程更适用于并发操作。

缺点:

由于缺少操作系统提供的保护机制,在多线程共享数据及通信时,就需要程序员做更多的工作以保证对共享数据段的操作是以预想的操作顺序进行的,并且要极力的避免死锁(deadlock)。

 

  • 单CPU内核的多个线程。(并发)

一个时间片运行一个线程的代码,并不是真正意义的并行计算。 

  • 多个CPU或者多个内核(并行)

可以做到真正的并行计算。 

 

1.2 创建线程

创建线程很简单,只需要把函数添加到线程当中即可。这里介绍用普通函数去创建线程。

形式1:

std::thread myThread ( thread_fun); //函数形式为void thread_fun()
myThread.join();                    //同一个函数可以代码复用,创建多个线程

形式2:

std::thread myThread ( thread_fun(100)); //函数形式为void thread_fun(int x)
myThread.join();                         //同一个函数可以代码复用,创建多个线程

形式3:

std::thread (thread_fun,1).detach(); //直接创建线程,没有名字
                                     //函数形式为void thread_fun(int x)

示例代码:

#include <iostream>
#include <thread>
using namespace std;

void thread_func1()//不带参数的线程处理函数
{
    cout << "子线程1" << endl;
}

void thread_func2(int x)//带参数的线程处理函数
{
    cout << "子线程2 " << "x = " << x << endl;
}

int main()
{
    thread t1(thread_func1);       //开启线程,调用:thread_func1()
    thread t2(thread_func2, 666);  //开启线程,调用:thread_func2(666)
    cout << "主线程" << endl;
    
    t1.join(); //必须说明添加线程的方式            
    t2.join();
    cout << "子线程结束" << endl;//必须join完成
    
    system("pause");
    return 0;
}

运行结果:

 

1.3 线程分离

线程分为主线程和子线程,对于线程的状态分为三种:主线程先运行结束,子线程先运行结束,主子线程同时结束。

在一些情况下需要在子线程结束后主线程才能结束,而一些情况则不需要等待,对于线程的处理方式有以下两种:

join

  • join()函数是一个等待线程完成函数,主线程需要等待子线程运行结束了才可以结束

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

void Main_Thread()//主线程
{
    cout << "主线程:干活..." << endl;
}

void Child_Thread()//子线程
{
    this_thread::sleep_for(chrono::seconds(2)); // 延时2秒
    cout << "子线程:睡觉..." << endl;
}

int main()            
{
    thread t1(Child_Thread);
    t1.join();        //等待子线程结束后才进入主线程
    Main_Thread();

    system("pause");
    return 0;
}

运行结果:

detach

  • detach()函数称为分离线程函数,使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束 通常称分离线程为守护线程(daemon threads)

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

void Main_Thread()//主线程
{
    cout << "主线程:干活..." << endl;
}

void Child_Thread()//子线程
{
    this_thread::sleep_for(chrono::seconds(2)); // 延时2秒
    cout << "子线程:睡觉..." << endl;
}

int main()            
{
    thread t1(Child_Thread);
    t1.detach();        //分离线程,主线程不会等待子线程运行结束
    Main_Thread();

    system("pause");
    return 0;
}

运行结果:

 

 

2. 创建线程方式

下面会介绍多种创建线程的方式

2.1 普通函数

#include <iostream>
#include <thread>
using namespace std;
​
//No.1 普通函数充当线程处理函数
void print()
{
    cout << "线程处理函数启动......" << endl;
}
void test1()
{
    thread t1(print);
    t1.join();
}
​
​
int main()            
{
    test1();
​
    return 0;
}

2.2 带参函数

#include <iostream>
#include <thread>
using namespace std;
​
//No.3 带参函数创建线程
//3.1 带参函数
void print(int num)
{
    cout << "id:" << num << endl;
}
void test3()
{
    int num = 1;
    thread t1(print, num);
    t1.join();
}
​
//3.2 带参函数(传引用)
void printReference(int& num)
{
    num = 1001;
    cout << "子线程:" << num << endl;
}
void test4()
{
    int num = 0;
    thread t1(printReference, ref(num));//需要传右值引用,使用ref()包装为右值引用
    t1.join();
    cout << "主线程:" << num << endl;
}
​
//3.3 智能指针当做函数参数
void printPtr(unique_ptr<int> ptr)
{
    cout << "智能指针:" << *ptr.get() << endl;
}
void test5()
{
    unique_ptr<int> ptr(new int(100));
    thread t1(printPtr, move(ptr));
    t1.join();
    cout << "test5:" << ptr.get() << endl;
}
​
​
int main()            
{
    test4();
    test5();
​
    return 0;
}

2.3 成员函数

#include <iostream>
#include <thread>
using namespace std;
​
//No.4 通过类中成员函数去创建
//4.1 仿函数形式:类名方式调用
class Function
{
public:
​
    void operator()()
    {
        cout << "重载()" << endl;
    }
};
​
void test6()
{
    //对象
    Function fun;
    thread t1(fun);
    t1.join();
    //匿名对象
    thread t2((Function()));
    t2.join();
}
​
//4.2 类中的成员函数
class MM
{
public:
    void print(int& id)
    {
        cout << "id:" << id << endl;
    }
​
};
void test7()
{
    MM mm;
    int id = 1;
    thread t1(&MM::print, mm, ref(id));
    t1.join();
}
​
​
int main()            
{
    test6();
    test7();
​
    return 0;
}

2.4 Lambda表达式

#include <iostream>
#include <thread>
using namespace std;
​
//No.2 Lambda表达式充当线程处理函数
void test2()
{
    thread t1([]() {cout << "Lambda表达式充当线程处理函数......" << endl; });
    t1.join();
}
​
​
int main()            
{
    test2();
​
    return 0;
}

 

3. 线程管理函数

3.1 get_id()

该函数在命名空间std::this_thread下。作用是获取当前线程的id。

#include <iostream>
#include <thread>
using namespace std;
 
//No.1 get_id() 获取线程id
void threadFunc() {
	cout << "get_id() 子线程id: " << this_thread::get_id() << endl;
	using namespace this_thread;
	cout << "get_id() 子线程id: " << get_id() << endl;
}
void test01() {
	cout << "主线程id: " << this_thread::get_id() << endl;
	thread t1(threadFunc);
	t1.join();
}
int main() {
 
    test01();
	return 0;
}

运行结果:

 

3.2 sleep_for()

该函数在命名空间std::this_thread下。作用是延时。 

#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
 
//No.2 sleep_for() 延时函数
void threadSleepFor() {
	using namespace this_thread;
	cout << "子线程id: " << get_id()  << " 线程启动!" << endl;
 
	this_thread::sleep_for(2s); //文本重载方式 延时两秒
 
	sleep_for(chrono::seconds(2)); //延时两秒
 
	using namespace chrono;
	sleep_for(seconds(2));
 
	cout << "子线程id: " << get_id() << " 线程结束!" << endl;
}
void test02() {
	thread t2(threadSleepFor);
	t2.join();
}
 
int main() {
 
    test02();
	return 0;
}

 线程启动后, 在线程处理函数中,会延时一段时间。延时结束后,继续执行未执行完的线程处理函数。

运行结果:

 

3.3 yield()

该函数在命名空间std::this_thread下。作用是让当前线程让步,让操作系统执行另一个线程。 

#include <iostream>
#include <thread>
#include <chrono>
#include <windows.h>
using namespace std;
 
//No.3 yield()	线程让步(让线程放弃执行, 让操作系统调用另一个线程执行)
void threadYield(chrono::milliseconds duration) {   //间隔时间ms
	using namespace this_thread;
	cout << "yield: 子线程id: " << get_id() << " 线程开始!" << endl;
 
    //使用高精度时钟获取当前时间
	auto startTime = chrono::high_resolution_clock::now();
	auto endTime = startTime + duration;
	do {
        //线程让步
		yield();
	} while (chrono::high_resolution_clock::now() < endTime);
 
	cout << "yield: 子线程id: " << get_id() << " 线程结束!" << endl;
}
//chrono::microseconds 微秒
void test03() {
	thread at[5];
    //线程1让步 5 秒
	at[0] = thread(threadYield, chrono::milliseconds(5000));
    //其余四个线程让步 0 秒(每隔一秒创建一个线程)
	for (int i = 1; i < 5; i++) {
		this_thread::sleep_for(1s);
		at[i] = thread(threadYield, chrono::milliseconds(0));
	}
	for (auto& th : at) {
		th.join();
	}
}
int main() {
 
	test03();
 
	return 0;
}

由下面的运行结果可知,第一个(线程id为12304)的 线程会等待5秒(线程让步5秒),

此时操作系统会执行下面的四个线程,待5秒之后,让步的线程(线程id为12304)的线程处理函数继续向下执行。

运行结果:

 

3.4 sleep_until()

该函数在命名空间std::this_thread下。作用是阻塞当前线程,直到sleep_time溢出。 

#include <iostream>
#include <thread>
#include <chrono>
#include <iomanip>
#include <windows.h>
using namespace std;
 
void threadFunc() {
	cout << "get_id() 子线程id: " << this_thread::get_id() << endl;
	using namespace this_thread;
	cout << "get_id() 子线程id: " << get_id() << endl;
}
 
//No.4 sleep_until() 阻塞当前执行线程 直到sleep_time溢出
void threadSleepUntil() {
	cout << "sleep_until: 子线程id: " << this_thread::get_id() << " 线程开始!" << endl;
 
	time_t tt = chrono::system_clock::to_time_t(chrono::system_clock::now());
	tm* ptm = new tm;
	localtime_s(ptm, &tt);
	cout << put_time(ptm, "%X") << endl;
	//设置sleep_time为5秒
	for (int i = 0; i < 5; i++)
	{
		++ptm->tm_sec;
	}
	if (ptm != nullptr) {
		this_thread::sleep_until(chrono::system_clock::from_time_t(mktime(ptm)));
	}
	cout << put_time(ptm, "%X") << endl;
 
	cout << "sleep_until: 子线程id: " << this_thread::get_id() << " 线程结束!" << endl;
}
void test04() {
	thread t1(threadSleepUntil);
    this_thread::sleep_for(1s);
	thread t2(threadFunc);
	t1.join();
	t2.join();
}
int main() {
	test04();
 
	return 0;
}

 由下面的运行结果可知,线程t1会进入阻塞状态(sleep_time)阻塞5秒钟,然后t2线程会执行,5秒后t1线程退出阻塞状态,继续执行t1线程的线程处理函数。 

运行结果:

 

posted @ 2023-10-11 16:57  [BORUTO]  阅读(104)  评论(0)    收藏  举报