主页 > 互联网  > 

C++11thread

C++11thread

文章目录 C++11 线程库线程对象的构造方式无参的构造函数调用带参的构造函数调用移动构造函数thread常用成员函数 this_thread命名空间join && detachmutex

C++11 线程库 线程对象的构造方式 无参的构造函数

1、调用无参的构造函数,调用无参的构造函数创建出来的线程对象没有关联任何线程函数,即没有启动任何线程

thread t1;

2、thread提供了移动赋值函数,当后续需要让该线程对象与线程函数关联时,可以以带参的方式创建一个匿名对象,然后调用移动赋值将该匿名对象关联线程的状态转移给该线程对象

void Print(size_t n , const std::string& s) { for (size_t i = 0; i < n; i++) { std::cout << std::this_thread::get_id() << ":" << i << std::endl; } } int main() { int n = 10; // 创建n个线程执行Print std::vector<std::thread> vthd(n); size_t j = 0; for (auto& thd : vthd) { // 移动赋值 thd = std::thread(Print, 10, "线程" + std::to_string(j++)); } for (auto& thd : vthd) { thd.join(); } return 0; }

场景:

实现线程池的时候, 需要先创建一批线程,但,一开始这些线程什么也不做,当有任务到来时再让这些线程来处理这些任务。

#include <iostream> #include <vector> #include <thread> #include <functional> #include <queue> #include <mutex> #include <condition_variable> #include <atomic> class ThreadPool { public: ThreadPool(size_t threads = 4); // 构造函数,可以指定线程池的大小 ~ThreadPool(); // 析构解函数 void enqueue(std::function<void()> task); // 添加任务到线程池 private: std::vector<std::thread> workers; // 线程向量 std::queue<std::function<void()>> tasks; // 任务队列队 std::mutex queue<std::function<void()>> completedTasks; std::condition_variable cond; // 条件变量 bool stop; // 停止标志 void work(); // 线程工作函数 }; // 构造函数初始化线程池 ThreadPool::ThreadPool(size_t threads) : stop(false) { for(size_t i = 0; i < threads; ++i) workers.emplace_back(std::thread(&ThreadPool::work, this)); } // 析构解函数等待所有线程完成 ThreadPool::~ThreadPool() { { std::unique_lock<std::mutex> lock(this->mtx); this->stop = true; } cond.notify_all(); for(std::thread &worker : workers) { if(worker.joinable()) { worker.join(); // 等待线程结束 } } } // 添加任务到线程池 void ThreadPool::enqueue(std::function<void()> task) { { std::unique_lock<std::mutex> lock(mtx); if(stop) throw std::runtime_error("enqueue on stopped ThreadPool"); tasks.emplace(task); } cond.notify_one(); } void ThreadPool::work() { while(true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(this->mtx); while(tasks.empty()) { cond.wait(lock); // 如果没有任务,等待 } task = std::move(tasks.front()); tasks.pop(); }(); task(); // 执行任务 } }

在这个示例中,ThreadPool类管理一组工作线程。构造函数创建指定数量的线程,每个线程都调用work方法等待并执行任务。enqueue方法用于添加新任务到队列,如果线程池已经停止,则抛出异常。每个工作线程在接收到任务后会出队列并执行它

调用带参的构造函数 template <class Fn, class... Args> explicit thread (Fn&& fn, Args&&... args); fn:可调用对象,比如函数指针、仿函数、lambda表达式、被包装器包装后的可调用对象等。args...:调用可调用对象fn时所需要的若干参数 调用移动构造函数

用一个右值线程对象来构造一个线程对象

void func(int n) { for (int i = 0; i <= n; i++) { cout << i << endl; } } int main() { thread t3 = thread(func, 10); t3.join(); return 0; }

线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。 如果创建线程对象时没有提供线程函数,那么该线程对象实际没有对应任何线程。 如果创建线程对象时提供了线程函数,那么就会启动一个线程来执行这个线程函数,该线程与主线程一起运行。 thread类是防拷贝的,不允许拷贝构造和拷贝赋值,但是可以移动构造和移动赋值,可以将一个线程对象关联线程的状态转移给其他线程对象,并且转移期间不影响线程的执行

thread常用成员函数

join ,对该线程进行等待,在等待的线程返回之前,调用join函数的线程将会被阻塞 joinable , 判断该线程是否已经执行完毕,如果是则返回true,否则返回false detach ,将该线程与创建线程进行分离,被分离后的线程不再需要创建线程调用join函数对其进行等待 get_id , 获取该线程的id swap , 将两个线程对象关联线程的状态进行交换

joinable函数还可以用于判定线程是否是有效的,

如果是以下任意情况,则线程无效:

采用无参构造函数构造的线程对象。(该线程对象没有关联任何线程) 线程对象的状态已经转移给其他线程对象。(已经将线程交给其他线程对象管理) 线程已经调用join或detach结束。(线程已经结束)

thread的成员函数get_id可以获取线程的id,但该方法必须通过线程对象来调用get_id函数

如果要在线程对象关联的线程函数中获取线程id,调用this_thread命名空间下的get_id函数

void func() { cout << this_thread::get_id() << endl; //获取线程id } int main() { thread t(func); t.join(); return 0; } this_thread命名空间 yield当前线程“放弃”执行,让操作系统调度另一线程继续执行sleep_until让当前线程休眠到一个具体时间点sleep_for让当前线程休眠一个时间段

线程传参

1、使用lambda

#include<thread> #include<iostream> #include<vector> #include<string> int main() { size_t n1 = 5; size_t n2 = 5; /*std::cin >> n1 >> n2;*/ std::thread t1( [n1](){ for (size_t i = 0; i < n1; i++) { //拿到该线程的线程id std::cout << std::this_thread::get_id() << ":" << i << " " << std::endl; } } ); std::thread t2([n2](){ for (size_t i = 0; i < n2; i++) { //拿到该线程的线程id std::cout << std::this_thread::get_id() << ":" << i << " " << std::endl; } } ); t2.join(); t1.join(); return 0; } join && detach

启动一个线程后,当这个线程退出时,需要对该线程所使用的资源进行回收,否则可能会导致内存泄露等问题。thread库提供了两种回收线程资源的方式:

1、join

主线程创建新线程后,调用join函数等待新线程终止,当新线程终止时join函数就会自动清理线程相关的资源

join函数清理线程的相关资源后,thread对象与已销毁的线程就没有关系了,因此一个线程对象一般只会使用一次join,如果一个线程对象使用多次join , 程序会崩溃

void func(int n) { for (int i = 0; i <= n; i++) { cout << i << endl; } } int main() { thread t(func, 20); t.join(); t.join(); //程序崩溃 return 0; }

2、detach

主线程创建新线程后,也可以调用detach函数将新线程与主线程进行分离,分离后新线程会在后台运行,其所有权和控制权将会交给C++运行库,此时C++运行库会保证当线程退出时,其相关资源能够被正确回收。

使用detach的方式回收线程的资源,一般在线程对象创建好之后就立即调用detach函数。 否则线程对象可能会因为某些原因,在后续调用detach函数分离线程之前被销毁掉,这时就会导致程序崩溃。 因为当线程对象被销毁时会调用thread的析构函数,而在thread的析构函数中会通过joinable判断这个线程是否需要被join,如果需要那么就会调用terminate终止当前程序(程序崩溃)

#include <iostream> #include <thread> #include <vector> void worker() { std::cout << "Working..." << std::endl; // 模拟耗时操作 std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "Finished" << std::endl; } int main() { std::vector<std::thread> threads; // 创建并分离多个线程 for (int i = 0; i < 5; ++i) { threads.emplace_back(std::thread(worker)); threads.back().detach(); // 分离线程 } std::cout << "Main thread continues to run..." << std::endl; // 主线程可以继续执行其他任务,而不需要等待分离的线程 // 等待所有线程完成(可选) for (auto& th : threads) { if (th.joinable()) { th.join(); } } std::cout << "All threads finished" << std::endl; return 0; }

过度使用分离线程可能会导致资源泄露,因为分离的线程将继续运行,即使主线程已经结束。因此,通常建议在可能的情况下使用join来管理线程的生命周期。

mutex

C++11中,mutex中总共包了四种互斥量

1、std::mute

mutex锁是C++11提供的最基本的互斥量,mutex对象之间不能进行拷贝,也不能进行移动

mutex常用的成员函数:

lock对互斥量进行加锁try_lock尝试对互斥量进行加锁unlock对互斥量进行解锁,释放互斥量的所有权

线程函数调用lock时,可能会发生以下三种情况:

1、如果该互斥量当前没有被其他线程锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一致拥有该锁。

#include <iostream> #include <thread> #include <mutex> std::mutex mtx; // 全局互斥锁 void printBlock(int n) { mtx.lock(); // 获取锁,如果锁已经被其他线程占用,则等待 for (int i = 0; i < n; ++i) { std::cout << "Thread " << std::this_thread::get_id() << " says " << i << std::endl; } mtx.unlock(); // 释放锁,其他线程可以获取该锁 } int main() { std::thread t1(printBlock, 1); std::thread t2(printBlock, 2); t1.join(); t2.join(); return 0; }

mtx是一个全局互斥锁,所以一次只能有一个线程执行printBlock函数。这意味着即使两个线程几乎同时运行,输出也将是交错的,而不是同时打印,因为一个线程在打印时会锁定互斥锁,另一个线程必须等待

2、如果该互斥量已经被其他线程锁住,则当前的调用线程会被阻塞。

互斥锁(mutex)在多线程环境中的基本行为。互斥锁是一种同步机制,用于防止多个线程同时访问共享资源,从而避免数据竞争条件和不一致的状态。当一个线程尝试获取已经被其他线程持有的互斥锁时,该线程将被阻塞,直到互斥锁被释放

#include <iostream> #include <thread> #include <mutex> #include <chrono> std::mutex mtx; // 全局互斥锁 void worker(int id) { // 尝试获取锁 mtx.lock(); std::cout << "Thread " << id << " acquired the lock" << std::endl; // 模拟耗时的工作 std::this_thread::sleep_for(std::chrono::milliseconds(500)); std::cout << "Thread " << id << " released the lock" << std::endl; // 释放锁 mtx.unlock(); } int main() { std::thread t1(worker, 1); std::thread t2(worker, 2); // 主线程稍作等待,以便 t1 有机会获取锁 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 尝试在 t1 持有锁时获取锁 mtx.lock(); std::cout << "Main thread acquired the lock" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟主线程持有锁 mtx.unlock(); t1.join(); t2.join(); return 0; }

3、如果该互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

如何使用局部变量加锁?

例1:用lambda

int main() { size_t n1 = 10000; size_t n2 = 10000; /*std::cin >> n1 >> n2;*/ int x = 0; std::mutex mtx; std::thread t1([&n1, &x ,&mtx]() { for (size_t i = 0; i < n1; i++) { mtx.lock(); x++; //拿到该线程的线程id //std::cout << std::this_thread::get_id() << ":" << i << " " << std::endl; mtx.unlock(); } }); std::thread t2([&n2, &x,&mtx]() { for (size_t i = 0; i < n2; i++) { mtx.lock(); //拿到该线程的线程id x++; //std::cout << std::this_thread::get_id() << ":" << i << " " << std::endl; mtx.unlock(); } }); t2.join(); t1.join(); std::cout << x; return 0; }

例2:

void Print1(size_t n,size_t j , const std::string& s ,std::mutex & mtx) //锁必须传引用 ,锁不支持拷贝 { for (size_t i = 0; i < j+n; i++) { mtx.lock(); std::cout << std::this_thread::get_id() << ":" << i << std::endl; mtx.unlock(); } } int main() { int n = 10; // 创建n个线程执行Print std::vector<std::thread> vthd(n); std::mutex mtx; std::thread t1(Print1, 100, 1, "hello", ref(mtx )); //必须加ref函数 ,否则锁不能以传引用的方式传参传过去 std::thread t2(Print1, 100, 100000, "world", ref(mtx)); t1.join(); t2.join(); return 0; }

例3

void Print1(size_t n, const std::string& s, std::mutex& mtx ,int & rx) //锁必须传引用 ,锁不支持拷贝 { for (size_t i = 0; i < n; i++) { mtx.lock(); std::cout << std::this_thread::get_id() << ":" << i << std::endl; ++rx; mtx.unlock(); std::this_thread::sleep_for(std::chrono::microseconds(1000)); } } int main() { std::mutex mtx; int x = 10; std::thread t1(Print1, 5, "hello", std::ref(mtx), std::ref(x)); std::thread t2(Print1,5, "world", std::ref(mtx), std::ref(x)); std::cout << "线程1: " << t1.get_id() << std::endl; std::cout << "线程2: " << t2.get_id() << std::endl; t1.join(); t2.join(); std::cout << x << std::endl; return 0; }

线程调用try_lock时,类似也可能会发生以下三种情况:

1、如果该互斥量当前没有被其他线程锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一致拥有该锁。

2、如果该互斥量已经被其他线程锁住,则try_lock调用返回false,当前的调用线程不会被阻塞。

3、如果该互斥量被当前调用线程锁住,则会产生死锁(deadlock)

标签:

C++11thread由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“C++11thread