一文详解C++多线程

您所在的位置:网站首页 多线程一定要加锁么 一文详解C++多线程

一文详解C++多线程

2023-12-26 01:07| 来源: 网络整理| 查看: 265

文章目录 1. 多线程1.1 多进程与多线程1.2 多线程理解1.3 创建线程1.4 join与detach方式(1)join举例(2)detach举例 1.5 this_thread 2. mutex2.1 lock与unlock2.2 lock_guard2.3 unique_lock 3. condition_variable3.1 wait3.2 wait_for 4. 线程池4.1 概念4.2 线程池的实现

1. 多线程

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

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

1.1 多进程与多线程 多进程并发

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

在进程间的通信,无论是使用信号、套接字,还是文件、管道等方式,其使用要么比较复杂,要么就是速度较慢或者两者兼而有之。运行多个线程的开销很大,操作系统要分配很多的资源来对这些进程进行管理。

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

多线程并发

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

优点:

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

缺点:

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

摘自《C++ 并发编程》

1.2 多线程理解 单CPU内核的多个线程。

一个时间片运行一个线程的代码,并不是真正意义的并行计算。 在这里插入图片描述

多个cpu或者多个内核

可以做到真正的并行计算。 在这里插入图片描述

1.3 创建线程

创建线程很简单,只需要把函数添加到线程当中即可。

形式1: std::thread myThread ( thread_fun);//函数形式为void thread_fun() myThread.join(); //同一个函数可以代码复用,创建多个线程 形式2: std::thread myThread ( thread_fun(100)); myThread.join(); //函数形式为void thread_fun(int x) //同一个函数可以代码复用,创建多个线程 形式3: std::thread (thread_fun,1).detach();//直接创建线程,没有名字 //函数形式为void thread_fun(int x) 代码举例

使用g++编译下列代码的方式:g++ test.cc -o test -l pthread

#include #include using namespace std; void thread_1() { cout thread first ( thread_1); // 开启线程,调用:thread_1() thread second (thread_2,100); // 开启线程,调用:thread_2(100) //thread third(thread_2,3);//开启第3个线程,共享thread_2函数。 std::cout //cout //cout std::cout cout cout std::cout std::cout // critical section (exclusive access to std::cout signaled by locking mtx): mtx_1.lock(); for (int i=0; i // critical section (exclusive access to std::cout signaled by locking mtx): mtx_2.lock(); test_num = 2; for (int i=0; i std::thread th1 (print_block_1,10000,'*'); std::thread th2 (print_block_2,10000,'$'); th1.join(); th2.join(); return 0; } 2.2 lock_guard

创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。

lock_guard的特点:

创建即加锁,作用域结束自动析构并解锁,无需手工解锁不能中途解锁,必须等作用域结束才解锁不能复制

代码举例

#include #include #include int g_i = 0; std::mutex g_i_mutex; // protects g_i,用来保护g_i void safe_increment() { const std::lock_guard lock(g_i_mutex); ++g_i; std::cout explicit Box(int num) : num_things{num} {} int num_things; std::mutex m; }; void transfer(Box &from, Box &to, int num) { // defer_lock表示暂时unlock,默认自动加锁 std::unique_lock lock1(from.m, std::defer_lock); std::unique_lock lock2(to.m, std::defer_lock); //两个同时加锁 std::lock(lock1, lock2);//或者使用lock1.lock() from.num_things -= num; to.num_things += num; //作用域结束自动解锁,也可以使用lock1.unlock()手动解锁 } int main() { Box acc1(100); Box acc2(50); std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10); std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5); t1.join(); t2.join(); std::cout return cargo!=0;} void consume (int n) { for (int i=0; i std::thread consumer_thread (consume,10); for (int i=0; i std::cin >> value; cv.notify_one(); } int main () { std::cout public: threadPool(int number = 1);//默认开一个线程 ~threadPool(); std::queue tasks_queue; //任务队列 bool append(T *request);//往请求队列<task_queue>中添加任务 private: //工作线程需要运行的函数,不断的从任务队列中取出并执行 static void *worker(void *arg); void run(); private: std::vector work_threads; //工作线程 std::mutex queue_mutex; std::condition_variable condition; //必须与unique_lock配合使用 bool stop; };//end class //构造函数,创建线程 template threadPool::threadPool(int number) : stop(false) { if (number MAX_THREADS) throw std::exception(); for (int i = 0; i std::unique_lock lock(queue_mutex); stop = true; condition.notify_all(); for (auto &ww : work_threads) ww.join();//可以在析构函数中join } //添加任务 template bool threadPool::append(T *request) { /*操作工作队列时一定要加锁,因为他被所有线程共享*/ queue_mutex.lock();//同一个类的锁 tasks_queue.push(request); queue_mutex.unlock(); condition.notify_one(); //线程池添加进去了任务,自然要通知等待的线程 return true; } //单个线程 template void *threadPool::worker(void *arg) { threadPool *pool = (threadPool *)arg; pool->run();//线程运行 return pool; } template void threadPool::run() { while (!stop) { std::unique_lock lk(this->queue_mutex); /* unique_lock() 出作用域会自动解锁 */ this->condition.wait(lk, [this] { return !this->tasks_queue.empty(); }); //如果任务为空,则wait,就停下来等待唤醒 //需要有任务,才启动该线程,不然就休眠 if (this->tasks_queue.empty())//任务为空,双重保障 { assert(0&&"断了");//实际上不会运行到这一步,因为任务为空,wait就休眠了。 continue; } else { T *request = tasks_queue.front(); tasks_queue.pop(); if (request)//来任务了,开始执行 request->process(); } } } #endif

说明:

构造函数创建所需要的线程数一个线程对应一个任务,任务随时可能完成,线程则可能休眠,所以任务用队列queue实现(线程数量有限),线程用采用wait机制。任务在不断的添加,有可能大于线程数,处于队首的任务先执行。只有添加任务(append)后,才开启线程condition.notify_one()。wait表示,任务为空时,则线程休眠,等待新任务的加入。添加任务时需要添加锁,因为共享资源。

测试代码:

#include "mythread.h" #include #include using namespace std; class Task { public: void process() { //cout threadPool pool(6);//6个线程,vector std::string str; while (1) { Task *tt = new Task(); //使用智能指针 pool.append(tt);//不停的添加任务,任务是队列queue,因为只有固定的线程数 cout


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3