精华内容
下载资源
问答
  • c++ 多线程
    千次阅读
    2022-04-12 21:19:03

    1.基础知识

    为了提高CPU的使用率,将某些需要耗时较多的任务或是大量IO操作(IO处理速度很慢),采用多线程可以适当的提高程序的执行效率。

    C++11之后有了std::thread库,需要引入头文件:
    先来介绍Thread的成员函数:

    # 用来查看当前线程的id
    thread::get_id()
    # 检查此线程是否还和主线程连接(已经完成join、detach的线程都是false)
    thread::joinable()
    # 将线程与主线程的连接切断,并且此线程会继续独立执行下去,直到执行结束时候释放分配的资源
    thread::detach()
    # 交换两个线程
    thread::swap()
    #############################################################
    std::this_thread 命名空间(Namespace)
    # 查看当前线程的id
    this_thread::get_id()
    # 暂时中断此线程,OS会调用其他线程执行
    this_thread::yield()
    # 设定一个时间,让此线程在指定的时刻后再继续执行
    this_thread::sleep_until()
    # 暂时中断此线程,等待指定的一段时间后才会被执行
    this_thread::sleep_for()
    

    接着来示范一些简单的线程实例操作

    • 建立线程、等待指定线程结束
      使用thread<线程名称>()建立一个线程,若要传入参数,可以在function后加入第二个参数。

    使用join()将将主线程暂停,等待指定的线程结束,主线程才会结束

    #include <iostream>
    #include <thread>
    
    void first_thread_job()
    {
    	cout << "This is the first thread"<<endl;
    }
    // 传入string x
    void second_thread_job(string x)
    {
    	cout<<"This is the second thread"<<x<<endl;
    }
    
    int main()
    {
    	//建立线程
    	thread first_thread(first_thread_job);
    	thread second_thread(second_thread_job, "abc");
    	
    	// 将主程序暂停,等待指定的线程结束
    	first_thread.join();
    	second_thread.join();
    	
    	return 0;
    }
    // =======output========
    // This is the first thread
    // This is the second thread abc
    

    2.同步机制(Synchronized)

    同步机制(Synchronized)有以下几种:互斥量(Mutex),讯号量(Semaphore),条件变量(Condition Variable),原子变量(Atomic),队列(Queue),事件(Event)

    2.1 Queue

    Thread无法回传值,所以要使用queue.push()将要传回的值存入queue,再用queue.pop()取出

    #include <iostream>
    #include <thread>
    #include <queue>
    
    using namespace std;
    queue<int> q1;
    queue<int>::size_type q1_size;
    
    void first_thread_job(int x)
    {
    	// 将元素放入queue
    	q1.push(x);
    	cout<<"This is the first thread"<<x<<endl;
    }
    int main()
    {
    	thread first_thread(first_thread_job, 2);
    	first_thread.join()
    	
    	//传回队列的第一个元素,并没有将此元素剔除队列
    	int a = q1.front();
    	cout<<"The first element is"<<a<<endl;
    	q1_size = q1.size();
    	cout<<"The queue1 length is"<<q1_size<<endl;
    	
    	//弹出队列的第一个元素
    	q1.pop();
    	q1_size = q1.size();
    	cout << "The queue2 length is "<<q1_size<<endl;
    	return 0;
    }
    // ========output========
    // This is the first thread 2
    // The first element is 2
    // The queue1 length is 1
    // The queue2 length is 0
    

    2.2 Lock

    当同时有几个Thread要用到同一个数据时候,为了不发生Race Condition的现象,需要使用lock()以及unlock()来将其锁定住,不让其他Thread执行,C++需要引入头文件

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    //定义lock
    mutex mu;
    
    void first_thread_job()
    {
    	mu.lock();
    	cout<<"This is the first thread"<<endl;
    	mu.unlock();
    }
    int main()
    {
    	thread first_thread(first_thread_job);
    	first_thread.join();
    	return 0;
    }
    

    除了Lock之外,mutex还提供了lock_guard以及unique_lock

    2.3 lock_guard

    采用RAII方法来对mutex对象进行自动加锁、解锁的动作,可以保证线程的安全

    #incldue <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    mutex g_mutex;
    
    void first_thread_job()
    {
    	lock_guard<mutex> lock(g_mutex);
    	cout<<"This is the firdt thread"<<endl;
    }
    int main()
    {
    	thread first_thread(first_thread_job);
    	first_thread.join();
    	return 0;
    }
    

    2.4 unique_lock

    独占所有权的方式对mutex对象进行自动加锁、解锁的动作,具有lock_guard的功能,而且更灵活,支持移动赋值的动作,还支持同时锁定多个mutex,但所花费的时间和内存更多,因此如果是lock_guard可以处理的线程,会尽量使用lock_guard。

    unique_lock写法跟lock_guard类似

    #incldue <iostream>
    #include <thread>
    #include <mutex>
    
    using namespace std;
    mutex u_mutex;
    
    void first_thread_job()
    {
    	unique_lock<mutex> lock(u_mutex);
    	cout<<"This is the firdt thread"<<endl;
    }
    int main()
    {
    	thread first_thread(first_thread_job);
    	first_thread.join();
    	return 0;
    }
    

    2.5 Semaphore

    mutex的扩充版本,可以允许多个线程同时执行,以下是Semaphore的基本代码

    # 初始化Semaphore
    sem_init(sem_t *sem, int pshared, unsigned int value);
    # 定义Semaphore的名称
    sem_t *sem
    # 设定为0,表示仅供目前的process及其Thread使用。非0表示此Semaphore与其他process共用
    int pshared
    # 设定Semaphore计数器
    unsigned int value
    # 用来阻塞该线程,直到Semaphore的值大于0,若解除阻塞后,Semaphore的值会减1,表示可执行的次数减1
    sem_wait(sem_t*sem)
    # 当有线程阻塞在信号上,调用此函数会使其中一个线程解除阻塞,此时Semaphore的值加1
    sem_post(sem_t *sem);
    # 删除Semaphore
    sem_destroy(sem_t*sem);
    

    接下来示范使用Semaphore

    #include <iostream>
    #include <thread>
    #include <semaphore.h>
    
    using namespace std;
    sem_t binSem;
    int a;
    
    void first_thread_job()
    {
    	sem_wait(&binSem);
    	for(int i = 0;i<3;i++)
    	{
    		a += 1;
    		cout << "This is the first thread"<<a<<endl;
    	}
    }
    
    void second_thread_job()
    {
    	for(int i=0;i<3;i++)
    	{
    		a -= 1;
    		cout << "This is the second thread"<<a<<endl;
    	}
    	sem_post(&binSem);
    }
    
    int main()
    {
    	int res;
    	//Semaphore初始化
    	res = sem_init(&binSem, 0, 0);
    	//建立线程
    	thread first_thread(first_thread_job);
    	thread second_thread(second_thread_job);
    	// 将主线程暂停,等待指定的线程结束
    	first_thread.join();
    	second_thread.join();
    	return 0;
    }
    
    // =======output======
    // first_thread_job 被阻塞,直到second_thread_job把信号加1,才开始执行
    // This is the second thread -1
    // This is the second thread -2
    // This is the second thread -3
    // This is the first thread -2
    // This is the first thread -1
    // This is the first thread 0
    

    2.6 Condition Variable

    用于等待的同步机制,能阻塞一个或多个线程,condition_variable提供wait()将线程停下来等待通知,直到接收到另一个线程发出的通知才会被唤醒,提供notify_one()或notify_all()两种函式。

    notify_one()只会通知其中一个正在等待的线程,notify_all()则会通知所有正在等待的线程,需要和mutex配合使用。

    其中wait()可以有两个参数,第一个参数是mutex,第二个参数是bool类型,当为true时候,线程才会停止等待,如果为false,线程则会等待下一次的通知

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include<condition_variable>
    using namespace std;
    condition_variable cond_var;
    mutex u_mutex;
    bool ready = false;
    void first_thread_job()
    {
    	 unique_lock<mutex> lock(u_mutex);
    	// 使用 wait() 进行等待
    	 cout << "thread wait" << endl;
    	 cond_var.wait(lock);
    	 cout << "This is the first thread " << endl;
    }
    void second_thread_job()
    {
    	 unique_lock<mutex> lock(u_mutex);
    	// 使用 wait() 进行等待 传入第二个参数
    	 cout << "thread wait" << endl;
    	 cond_var.wait(lock, [](){ return ready; });
    	 cout << "This is the second thread " << endl;
    }
    int main()
    {
     thread first_thread(first_thread_job);
     thread second_thread(second_thread_job);
     cout << "wait 5 millisecond…" << endl;
     this_thread::sleep_for(std::chrono::milliseconds(5));
    // 使用 notify_one() 唤醒线程
     cout << "thread notify_one" << endl;
     cond_var.notify_one();
     // 回传ready判断是否要停止等待
     ready = true;
    // 使用 notify_one() 唤醒线程
     cout << "thread notify_one" << endl;
     cond_var.notify_one();
     first_thread.join();
     second_thread.join();
     return 0;
    }
    

    除了wait()以外,还有提供wait_for()和wait_until()这两个函数,限制等待的时间。wait_for()需要指定给定长度的时间,wait_until()则是需要指定一个时间点,时间的形式要使用STL chrono

    cond_var.wait_for(lock, chrono::seconds(5))
    cond_var.wait_until(lock, chrono::system_clock::now()+chrono::seconds(5))
    

    2.7 Atomic

    在C++11中引入原子操作的概念,提供更简单的机制确保线程的安全与存取共享变量的正确性,在任意时刻只有一个线程能存取这个资源,有点类似于互斥的保护机制,但原子操作比锁的使用效率更高。

    #include <iostream>
    #include <thread>
    #include <atomic>
    using namespace std;
    
    //定义原子变量
    atomic_int atomic_a(0);
    
    void first_thread_job()
    {
    	for(int i=0;i<3;i++)
    	{
    		atomic_a += 1;
    		cout << "This is the first thread "<< atomic_a << endl;
    	}
    }
    int main()
    {
     // 建立執行緒
     thread first_thread(first_thread_job);
     first_thread.join();
     return 0;
    }
    // ====== output ======
    // This is the first thread 1
    // This is the first thread 2
    // This is the first thread 3
    

    参考目录

    https://medium.com/ching-i/%E5%A4%9A%E5%9F%B7%E8%A1%8C%E7%B7%92-c-thread-9f6e37c7cf32

    更多相关内容
  • C++多线程编程.pdf

    2021-09-05 20:57:31
    C++多线程编程
  • P303.zip
  • C++ 多线程监听代码

    2020-10-12 10:49:33
    多线程监听代码,不同的线程来监听主线程是否发生变换,如果变换,根据相应的参数调用不同的线程执行相关代码
  • C++多线程入门[整理].pdf
  • c++ 多线程向同一个文本写入信息 解决同步与互斥问题
  • 配合文章代码实现 c++ 多线程调用嵌入的Python 文章详情 https://blog.csdn.net/u010383605/article/details/82057084 Python下载地址: https://www.python.org/downloads/windows/
  • 在做多线程编程时,有两个场景我们都会遇到: 多线程访问共享资源,需要用到锁; 多线程间的状态同步,这个可用的机制很多,条件变量是广泛使用的一种。 今天我用一个简单的例子来给大家介绍下锁和条件变量的使用...
  • C++多线程的十个例子,学习window下多线程编程
  • C++多线程编程

    2018-11-28 22:09:29
    C++多线程编程,详细介绍C++多线程中的并发并行以及同步
  • C++本身并没有提供任何多线程机制,但是在windows下,我们可以调用SDK win32 api来编写多线程的程序,下面就此简单的讲一下: 创建线程的函数 代码如下: HANDLE CreateThread(  LPSECURITY_ATTRIBUTES ...
  • 一、死锁会在什么情况发生 1、假设有如下代码 mutex; //代表一个全局互斥对象 void A() { mutex.lock(); //这里操作共享数据 B(); //这里调用B方法 mutex.unlock(); return;... //代表一个全
  • c++多线程编程

    2018-09-18 23:31:14
    本文简介了pthread的用法,介绍了多核多线程编程的入门知识
  • C++多线程编程实战

    2017-07-30 16:53:34
    C++多线程 windows
  • 本资源描述了C++11 中多线程的创建,C++11中std命名空间中将boost库中的Thread加入,boost多线程从准标准变为标准,其中还介绍了C++ 多线程下的单例模式的使用,本文档为txt文档
  • c++ 多线程

    2017-11-02 14:27:05
    c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程c++ 多线程...
  • C++多线程编程实战 姜佑译.pdf

    千次下载 热门讨论 2017-02-18 10:07:53
    C++多线程编程实战 姜佑译.pdf
  • 操作系统层面上关于多线程多线程协作的接口原理,线程的基本概念及常见问题,线程的创建与使用,线程 ID 的用途及原理
  • 由浅入深的介绍 linux windows下多线程程序设计,线程池模型设计,针对多线程编程,详细地介绍 Windows 和 Linux操作系统层面上提供的各种多线程接口,理解并熟悉它们的使用操作系统层面上关于多线程多线程协作的...
  • C++多线程详细讲解

    万次阅读 多人点赞 2021-03-19 20:33:26
    C++多线程基础教程 目录 1 什么是C++多线程? 2 C++多线程基础知识 2.1 创建线程 2.2 互斥量使用 lock()与unlock(): lock_guard(): unique_lock: condition_variable: 2.3 异步线程 async与future: shared_future ...

    本文是纯转载,觉得大佬写的非常好!如有侵权可以删除
    链接: link.

    C++多线程基础教程
    目录
    1 什么是C++多线程?
    2 C++多线程基础知识
    2.1 创建线程
    2.2 互斥量使用
    lock()与unlock():
    lock_guard():
    unique_lock:
    condition_variable:
    2.3 异步线程
    async与future:
    shared_future
    2.4 原子类型automic
    实例
    生产者消费者问题
    4 C++多线程高级知识
    4.1 线程池
    线程池基础知识
    线程池的实现
    5 延伸拓展
    最后一次更新日期:2020.08.23

    1 什么是C++多线程?

    线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,进程包含一个或者多个线程。进程可以理解为完成一件事的完整解决方案,而线程可以理解为这个解决方案中的的一个步骤,可能这个解决方案就这只有一个步骤,也可能这个解决方案有多个步骤。
    多线程:多线程是实现并发(并行)的手段,并发(并行)即多个线程同时执行,一般而言,多线程就是把执行一件事情的完整步骤拆分为多个子步骤,然后使得这多个步骤同时执行。
    C++多线程:(简单情况下)C++多线程使用多个函数实现各自功能,然后将不同函数生成不同线程,并同时执行这些线程(不同线程可能存在一定程度的执行先后顺序,但总体上可以看做同时执行)。
    上述概念很容易因表述不准确而造成误解,这里没有深究线程与进程,并发与并行的概念,以上仅为一种便于理解的表述,如果有任何问题还请指正,若有更好的表述,也欢迎留言分享。

    2 C++多线程基础知识

    2.1 创建线程

    首先要引入头文件#include(C++11的标准库中提供了多线程库),该头文件中定义了thread类,创建一个线程即实例化一个该类的对象,实例化对象时候调用的构造函数需要传递一个参数,该参数就是函数名,thread th1(proc1);如果传递进去的函数本身需要传递参数,实例化对象时将这些参数按序写到函数名后面,thread th1(proc1,a,b);只要创建了线程对象(传递“函数名/可调用对象”作为参数的情况下),线程就开始执行(std::thread 有一个无参构造函数重载的版本,不会创建底层的线程)。
    有两种线程阻塞方法join()与detach(),阻塞线程的目的是调节各线程的先后执行顺序,这里重点讲join()方法,不推荐使用detach(),detach()使用不当会发生引用对象失效的错误。当线程启动后,一定要在和线程相关联的thread对象销毁前,对线程运用join()或者detach()。
    join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续。th1.join(),即该语句所在的线程(该语句写在main()函数里面,即主线程内部)暂停,等待指定线程(指定线程为th1)执行结束后,主线程再继续执行。
    整个过程就相当于你在做某件事情,中途你让老王帮你办一个任务(你办的时候他同时办)(创建线程1),又叫老李帮你办一件任务(创建线程2),现在你的这部分工作做完了,需要用到他们的结果,只需要等待老王和老李处理完(join(),阻塞主线程),等他们把任务做完(子线程运行结束),你又可以开始你手头的工作了(主线程不再阻塞)。

    #include<iostream>
    #include<thread>
    using namespace std;
    void proc(int a)
    {
        cout << "我是子线程,传入参数为" << a << endl;
        cout << "子线程中显示子线程id为" << this_thread::get_id()<< endl;
    }
    int main()
    {
        cout << "我是主线程" << endl;
        int a = 9;
        thread th2(proc,a);//第一个参数为函数名,第二个参数为该函数的第一个参数,如果该函数接收多个参数就依次写在后面。此时线程开始执行。
        cout << "主线程中显示子线程id为" << th2.get_id() << endl;
        th2.join()//此时主线程被阻塞直至子线程执行结束。
        return 0;
    }
    

    2.2 互斥量使用

    什么是互斥量?

    这样比喻,单位上有一台打印机(共享数据a),你要用打印机(线程1要操作数据a),同事老王也要用打印机(线程2也要操作数据a),但是打印机同一时间只能给一个人用,此时,规定不管是谁,在用打印机之前都要向领导申请许可证(lock),用完后再向领导归还许可证(unlock),许可证总共只有一个,没有许可证的人就等着在用打印机的同事用完后才能申请许可证(阻塞,线程1lock互斥量后其他线程就无法lock,只能等线程1unlock后,其他线程才能lock),那么,这个许可证就是互斥量。互斥量保证了使用打印机这一过程不被打断。

    程序实例化mutex对象m,线程调用成员函数m.lock()会发生下面 3 种情况:
    (1)如果该互斥量当前未上锁,则调用线程将该互斥量锁住,直到调用unlock()之前,该线程一直拥有该锁。
    (2)如果该互斥量当前被锁住,则调用线程被阻塞,直至该互斥量被解锁。

    互斥量怎么使用?

    首先需要#include

    lock()与unlock():

    #include<iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    mutex m;//实例化m对象,不要理解为定义变量
    void proc1(int a)
    {
        m.lock();
        cout << "proc1函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 2 << endl;
        m.unlock();
    }
    
    void proc2(int a)
    {
        m.lock();
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
        m.unlock();
    }
    int main()
    {
        int a = 0;
        thread proc1(proc1, a);
        thread proc2(proc2, a);
        proc1.join();
        proc2.join();
        return 0;
    }
    

    不推荐实直接去调用成员函数lock(),因为如果忘记unlock(),将导致锁无法释放,使用lock_guard或者unique_lock能避免忘记解锁这种问题。

    lock_guard():
    其原理是:声明一个局部的lock_guard对象,在其构造函数中进行加锁,在其析构函数中进行解锁。最终的结果就是:创建即加锁,作用域结束自动解锁。从而使用lock_guard()就可以替代lock()与unlock()。
    通过设定作用域,使得lock_guard在合适的地方被析构(在互斥量锁定到互斥量解锁之间的代码叫做临界区(需要互斥访问共享资源的那段代码称为临界区),临界区范围应该尽可能的小,即lock互斥量后应该尽早unlock),通过使用{}来调整作用域范围,可使得互斥量m在合适的地方被解锁:

    #include<iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    mutex m;//实例化m对象,不要理解为定义变量
    void proc1(int a)
    {
        lock_guard<mutex> g1(m);//用此语句替换了m.lock();lock_guard传入一个参数时,该参数为互斥量,此时调用了lock_guard的构造函数,申请锁定m
        cout << "proc1函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 2 << endl;
    }//此时不需要写m.unlock(),g1出了作用域被释放,自动调用析构函数,于是m被解锁
    
    void proc2(int a)
    {
        {
            lock_guard<mutex> g2(m);
            cout << "proc2函数正在改写a" << endl;
            cout << "原始a为" << a << endl;
            cout << "现在a为" << a + 1 << endl;
        }//通过使用{}来调整作用域范围,可使得m在合适的地方被解锁
        cout << "作用域外的内容3" << endl;
        cout << "作用域外的内容4" << endl;
        cout << "作用域外的内容5" << endl;
    }
    int main()
    {
        int a = 0;
        thread proc1(proc1, a);
        thread proc2(proc2, a);
        proc1.join();
        proc2.join();
        return 0;
    }
    

    lock_gurad也可以传入两个参数,第一个参数为adopt_lock标识时,表示不再构造函数中不再进行互斥量锁定,因此此时需要提前手动锁定。

    #include<iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    mutex m;//实例化m对象,不要理解为定义变量
    void proc1(int a)
    {
        m.lock();//手动锁定
        lock_guard<mutex> g1(m,adopt_lock);
        cout << "proc1函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 2 << endl;
    }//自动解锁
    
    void proc2(int a)
    {
        lock_guard<mutex> g2(m);//自动锁定
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }//自动解锁
    int main()
    {
        int a = 0;
        thread proc1(proc1, a);
        thread proc2(proc2, a);
        proc1.join();
        proc2.join();
        return 0;
    }
    

    unique_lock:
    unique_lock类似于lock_guard,只是unique_lock用法更加丰富,同时支持lock_guard()的原有功能。
    使用lock_guard后不能手动lock()与手动unlock();使用unique_lock后可以手动lock()与手动unlock();
    unique_lock的第二个参数,除了可以是adopt_lock,还可以是try_to_lock与defer_lock;
    try_to_lock: 尝试去锁定,得保证锁处于unlock的状态,然后尝试现在能不能获得锁;尝试用mutx的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里
    defer_lock: 始化了一个没有加锁的mutex;

    lock_guard unique_lock
    手动lock与手动unlock 不支持 支持
    参数 支持adopt_lock 支持adopt_lock/try_to_lock/defer_lock

    #include<iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    mutex m;
    void proc1(int a)
    {
        unique_lock<mutex> g1(m, defer_lock);//始化了一个没有加锁的mutex
        cout << "不拉不拉不拉" << endl;
        g1.lock();//手动加锁,注意,不是m.lock();注意,不是m.lock();注意,不是m.lock()
        cout << "proc1函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 2 << endl;
        g1.unlock();//临时解锁
        cout << "不拉不拉不拉"  << endl;
        g1.lock();
        cout << "不拉不拉不拉" << endl;
    }//自动解锁
    
    void proc2(int a)
    {
        unique_lock<mutex> g2(m,try_to_lock);//尝试加锁,但如果没有锁定成功,会立即返回,不会阻塞在那里;
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }//自动解锁
    int main()
    {
        int a = 0;
        thread proc1(proc1, a);
        thread proc2(proc2, a);
        proc1.join();
        proc2.join();
        return 0;
    }
    unique_lock所有权的转移
    
    mutex m;
    {  
        unique_lock<mutex> g2(m,defer_lock);
        unique_lock<mutex> g3(move(g2));//所有权转移,此时由g3来管理互斥量m
        g3.lock();
        g3.unlock();
        g3.lock();
    }
    

    condition_variable:
    需要#include<condition_variable>;
    wait(locker):在线程被阻塞时,该函数会自动调用 locker.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数此时再自动调用 locker.lock()。
    notify_all():随机唤醒一个等待的线程
    notify_once():唤醒所有等待的线程

    2.3 异步线程

    需要#include

    async与future:
    async是一个函数模板,用来启动一个异步任务,它返回一个future类模板对象,future对象起到了占位的作用,刚实例化的future是没有储存值的,但在调用future对象的get()成员函数时,主线程会被阻塞直到异步线程执行结束,并把返回结果传递给future,即通过FutureObject.get()获取函数返回值。

    相当于你去办政府办业务(主线程),把资料交给了前台,前台安排了人员去给你办理(async创建子线程),前台给了你一个单据(future对象),说你的业务正在给你办(子线程正在运行),等段时间你再过来凭这个单据取结果。过了段时间,你去前台取结果,但是结果还没出来(子线程还没return),你就在前台等着(阻塞),直到你拿到结果(get())你才离开(不再阻塞)。

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include<future>
    #include<Windows.h>
    using namespace std;
    double t1(const double a, const double b)
    {
    	double c = a + b;
    	Sleep(3000);//假设t1函数是个复杂的计算过程,需要消耗3秒
    	return c;
    }
    
    int main() 
    {
    	double a = 2.3;
    	double b = 6.7;
    	future<double> fu = async(t1, a, b);//创建异步线程线程,并将线程的执行结果用fu占位;
    	cout << "正在进行计算" << endl;
    	cout << "计算结果马上就准备好,请您耐心等待" << endl;
    	cout << "计算结果:" << fu.get() << endl;//阻塞主线程,直至异步线程return
            //cout << "计算结果:" << fu.get() << endl;//取消该语句注释后运行会报错,因为future对象的get()方法只能调用一次。
    	return 0;
    }
    

    shared_future
    future与shard_future的用途都是为了占位,但是两者有些许差别。
    future的get()成员函数是转移数据所有权;shared_future的get()成员函数是复制数据。
    因此:
    future对象的get()只能调用一次;无法实现多个线程等待同一个异步线程,一旦其中一个线程获取了异步线程的返回值,其他线程就无法再次获取。
    shared_future对象的get()可以调用多次;可以实现多个线程等待同一个异步线程,每个线程都可以获取异步线程的返回值。

    future shared_future
    语义 转移 赋值
    可否多次调用 否 可

    2.4 原子类型automic

    原子操作指“不可分割的操作”;也就是说这种操作状态要么是完成的,要么是没完成的。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。
    automic是一个模板类,使用该模板类实例化的对象,提供了一些保证原子性的成员函数来实现共享数据的常用操作。

    可以这样理解:
    在以前,定义了一个共享的变量(int i=0),多个线程会操作这个变量,那么每次操作这个变量时,都是用lock加锁,操作完毕使用unlock解锁,以保证线程之间不会冲突;
    现在,实例化了一个类对象(automic I=0)来代替以前的那个变量,每次操作这个对象时,就不用lock与unlock,这个对象自身就具有原子性,以保证线程之间不会冲突。

    automic对象提供了常见的原子操作(通过调用成员函数实现对数据的原子操作):
    store是原子写操作,load是原子读操作。exchange是于两个数值进行交换的原子操作。
    即使使用了automic,也要注意执行的操作是否支持原子性。一般atomic原子操作,针对++,–,+=,-=,&=,|=,^=是支持的。

    实例
    前一章内容为了简单的说明一些函数的用法,所列举的例子有些牵强,因此在本章列举了一些多线程常见的实例

    生产者消费者问题
    /*

    生产者消费者问题
    */
    #include <iostream>
    #include <deque>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    #include<Windows.h>
    using namespace std;
    
    deque<int> q;
    mutex mu;
    condition_variable cond;
    int c = 0;//缓冲区的产品个数
    
    void producer() { 
    	int data1;
    	while (1) {//通过外层循环,能保证生成用不停止
    		if(c < 3) {//限流
    			{
    				data1 = rand();
    				unique_lock<mutex> locker(mu);//锁
    				q.push_front(data1);
    				cout << "存了" << data1 << endl;
    				cond.notify_one();  // 通知取
    				++c;
    			}
    			Sleep(500);
    		}
    	}
    }
    
    void consumer() {
    	int data2;//data用来覆盖存放取的数据
    	while (1) {
    		{
    			unique_lock<mutex> locker(mu);
    			while(q.empty())
    				cond.wait(locker); //wati()阻塞前先会解锁,解锁后生产者才能获得锁来放产品到缓冲区;生产者notify后,将不再阻塞,且自动又获得了锁。
    			data2 = q.back();//取的第一步
    			q.pop_back();//取的第二步
    			cout << "取了" << data2<<endl;
    			--c;
    		}
    		Sleep(1500);
    	}
    }
    int main() {
    	thread t1(producer);
    	thread t2(consumer);
    	t1.join();
    	t2.join();
    	return 0;
    }
    

    4 C++多线程高级知识

    4.1 线程池

    线程池基础知识
    不采用线程池时:

    创建线程 -> 由该线程执行任务 -> 任务执行完毕后销毁线程。即使需要使用到大量线程,每个线程都要按照这个流程来创建、执行与销毁。

    虽然创建与销毁线程消耗的时间 远小于 线程执行的时间,但是对于需要频繁创建大量线程的任务,创建与销毁线程 所占用的时间与CPU资源也会有很大占比。

    为了减少创建与销毁线程所带来的时间消耗与资源消耗,因此采用线程池的策略:

    程序启动后,预先创建一定数量的线程放入空闲队列中,这些线程都是处于阻塞状态,基本不消耗CPU,只占用较小的内存空间。

    接收到任务后,线程池选择一个空闲线程来执行此任务。

    任务执行完毕后,不销毁线程,线程继续保持在池中等待下一次的任务。

    线程池所解决的问题:

    (1) 需要频繁创建与销毁大量线程的情况下,减少了创建与销毁线程带来的时间开销和CPU资源占用。(省时省力)

    (2) 实时性要求较高的情况下,由于大量线程预先就创建好了,接到任务就能马上从线程池中调用线程来处理任务,略过了创建线程这一步骤,提高了实时性。(实时)

    线程池的实现
    待更新。

    延伸拓展

    创建类,除了传递函数外,还可以使用:Lambda表达式、可调用类的实例。
    线程与进程:
    并发与并行:
    并发与并行并不是非此即彼的概念
    并发:同一时间发生两件及以上的事情。
    线程并不是越多越好,每个线程都需要一个独立的堆栈空间,线程切换也会耗费时间。
    并行:

    detach():

    未完待续

    展开全文
  • C++多线程问题汇总

    2018-04-10 15:12:01
    C++多线程常见问题汇总,快来学习吧~~~~~~~~~~~~~~~~~~~~~~~~
  • C++多线程编程.7z

    2019-05-26 22:05:32
    这是一本以实践为主的Windows多线程编程指导。主要介绍了C++编程语言的概念和特性,介绍了进程,线程,同步,并发的相关知识,并介绍了.NET框架中的线程,概述了C++/CLI.NET线程对象。
  • C++多线程,消息队列用法,为了凑够20个字,拼了。
  • C++多线程总结[归纳].pdf
  • C++多线程编程实战 ,姜佑译(2018年PDF高清).rar C++多线程编程实战 ,姜佑译(2018年PDF高清).rar
  • 基于VS2010的C++多线程开发Demo 1.基于Dialog开发应用程序,包含全部工程。 2.包含2个独立线程,涵盖线程创建,收发信号,信号挂起,唤醒 3.界面操作,易于理解掌握。
  • 掌握C++多线程操作必读 Mastering C++ Multithreading A comprehensive guide to developing effective multithreading applications in C++
  • C++多线程SOCKET收发

    2014-07-08 16:47:25
    C++多线程SOCKET收发纯手工打造,网上的例子和解释都不行~既可以学习多线程操作,又可以实现SOCLET编程

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 351,692
精华内容 140,676
关键字:

c++ 多线程

友情链接: CommonMeasure.rar