C++多线程

介绍

C++多线程是指在C++程序中使用多个线程来实现并发执行的功能。多线程可以提高程序的性能和响应速度,同时也可以简化程序的设计和实现。

C++中,可以使用标准库中的thread类来创建和管理线程。thread类提供了多个构造函数和成员函数,可以用于创建新线程、等待线程结束、获取线程ID等操作

#include <iostream>
#include <chrono>

using namespace std::chrono;
using namespace std;

using ull = unsigned long long;
ull OddSum = 0;
ull EvenSum = 0;

void findEven(ull start, ull end) {
for (ull i = start; i <= end; ++i)
# i 的二进制表示中最低位与 1 的按位与
if ((i & 1) == 0)
EvenSum += i;
}

void findOdd(ull start, ull end) {
for (ull i = start; i <= end; ++i)
if ((i & 1) == 1)
OddSum += i;
}

int main() {

ull start = 0, end = 1900000000;

auto startTime = high_resolution_clock::now();
findOdd(start, end);
findEven(start, end);
auto stopTime = high_resolution_clock::now();
auto duration = duration_cast<microseconds>(stopTime - startTime);

cout << "OddSum : " << OddSum << endl;
cout << "EvenSum: " << EvenSum << endl;
cout << "Sec: " << duration.count() / 1000000 << endl;
return 0;
}

创建线程的五种类型

  1. function_pointer

// 1.函数指针
#include <thread>
#include <iostream>

using namespace std;

void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
// 注意:如果我们创建多线程 并不会保证哪一个先开始
int main() {
std::thread t1(fun, 10);
// std::thread t2(fun, 10);
t1.join();
// t2.join();
return 0;
}

C++ 中,函数名可以被视为指向函数代码的指针。在这里,fun 函数被用作线程的入口函数,它会在新的线程中被执行。在创建线程时,我们将 fun 函数的地址作为参数传递给了 std::thread 构造函数。

  1. lambda_function

// 1.函数指针
#include <thread>
#include <iostream>

using namespace std;

// 注意:如果我们创建多线程 并不会保证哪一个先开始
int main() {
// 2.Lambda函数
auto fun = [](int x) {
while (x-- > 0) {
cout << x << endl;
}
};
// std::1.thread t1(fun, 10);
// 也可以写成下面:
std::thread t1_1([](int x) {
while (x-- > 0) {
cout << x << endl;
}
}, 11);
// std::1.thread t2(fun, 10);
// t1.join();
t1_1.join();
// t2.join();
return 0;
}
  1. functor

#include <thread>
#include <iostream>

using namespace std;

// 3.functor (Funciton Object)
class Base {
public:
void operator()(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};

int main() {
thread t(Base(), 10);
t.join();
return 0;
}
  1. no_static_member_function

#include <thread>
#include <iostream>

using namespace std;

// 4.Non-static member function
class Base {
public:
void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};

int main() {
Base b;
thread t(&Base::fun,&b, 10);
t.join();
return 0;
}

函数名与函数指针的相关解释

第一种:函数名与FunP函数指针都是函数指针。fun是一个函数指针常量,funP是一个函数数指针变量。

虽然通过常量与变量来解释函数名无法赋值可以帮助理解,但是我们发现对fun赋值时编译器给的错误提示并不是说对常量进行赋值,而是告诉我们=号两端格式不匹配。对此,第二种理解更合理。

第二种:函数名和数组名实际上都不是指针,但是,在使用时可以退化成指针,即编译器可以帮助我们实现自动的转换。

这也可以解释为什么当我们在=号右侧使用函数名时,无论是取值还是取地址都没有问题,因为编译替我们做了相当于强制类型转换的工作,而在当函数名在=号左侧时,右侧的函数指针并没有这个功能,毕竟他们俩不是同一种结构。

  1. static_member_function

非静态成员函数只能通过类的对象进行调用,因为它们需要访问对象的状态。这与静态成员函数不同,后者可以直接通过类名进行调用

#include <thread>
#include <iostream>

using namespace std;

// 4.static member function
class Base {
public:
static void fun(int x) {
while (x-- > 0) {
cout << x << endl;
}
}
};

int main() {
thread t(&Base::fun, 10);
t.join();
return 0;
}

Join 与 Detachs

  1. join

  • 一旦线程开始,我们要想等待线程完成,需要在该对象上调用join()双重join将导致程序终止

    调用 join() 后会进入“已加入状态”,即该线程对象已经完成其任务并已经被系统回收。如果再次调用该线程对象的 join(),会导致程序崩溃。

  • 在join之前我们应该检查显示是否可以被join,通过使用joinable()

#include <iostream>
#include <thread>
#include <chrono>

using namespace std;


void run(int count) {
while (count-- > 0) {
cout << count << endl;
}
std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
thread t1(run, 10);
cout << "main()" << endl;
t1.join();
if (t1.joinable()) {
t1.join();
}
cout << "main() after" << endl;
return 0;
}
  1. detach

  • 这用于从父线程分离新创建的线程

  • 在分离线程之前,请务必检查它是否可以joinable,否则可能会导致两次分离,并且双重detach()将导致程序终止

  • 如果我们有分离的线程并且main函数正在返回,那么分离的线程执行将被挂起

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

void run(int count) {
while (count-- > 0) {
cout << count << endl;
}
std::this_thread::sleep_for(chrono::seconds(3));
}

int main() {
thread t1(run, 10);
cout << "main()" << endl;
t1.detach();
if(t1.joinable())
t1.detach();
cout << "main() after" << endl;
return 0;
}

mutex in C++ Threading

  • critical_section

  1. 这段代码创建了两个线程t1t2,它们都会执行countgold()函数,用于计算一个共享变量sum的值。countgold()函数使用了一个互斥锁来保护共享变量sum的访问。

  2. main()函数启动了两个线程,并等待它们完成。一旦两个线程都完成了计算,程序就会输出sum的最终值。由于使用了互斥锁,程序可以保证每次只有一个线程能够修改sum变量,因此最终结果是可预测的。

#include <iostream>
#include <mutex>
#include <thread>

using namespace std;

int sum = 0; //shared

mutex m;

void *countgold() {
int i; //local to each thread
for (i = 0; i < 10000000; i++) {
m.lock();
sum += 1;
m.unlock();
}
return NULL;
}

int main() {
thread t1(countgold);
thread t2(countgold);

//Wait for both threads to finish
t1.join();
t2.join();

cout << "sum = " << sum << endl;
return 0;
}

mutex提供了两个主要的操作:lock()unlock()。在访问共享资源之前,线程需要调用lock()函数来获取互斥锁;在访问完成之后,线程需要调用unlock()函数来释放互斥锁。当一个线程已经获取了互斥锁时,其他线程将无法获取该锁,直到该线程释放了锁。这样可以保证在任何时候只有一个线程能够访问共享资源,从而避免了竞态条件的发生。

Reference

[1] C++ 那些事: https://github.com/Light-City/CPlusPlusThings

------------------------------- 本文结束啦❤感谢您阅读-------------------------------
赞赏一杯咖啡