c++11线程基本用法

  • 简单示例

    1. thread和join()

      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
      	cout << "我的线程执行" << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "我的线程执行完毕" << endl;
      }
      int main() {
          //thread是标准库的类,test为可调用对象,作为线程执行起点
      	thread th(test);
          //join():阻塞主线程,让主线程等待子线程执行完毕
      	th.join();
      	cout << "主线程执行结束" << endl;
      	return 0;
      }
      

      在这里插入图片描述

    2. detach()

      //传统多线程程序主线程要等待子线程执行完毕后才能退出
      //detach():将主线程和子线程分离。可以让主线程不必等待子线程
      //一旦detach()之后,与主线程关联的thread对象就会视区和主线程的关联,此时这个子线程就会在后台运行。当子线程执行完成后,由语和女性时库负责清理该线程相关资源
      //detach()会让子线程失去我们的控制,使用detach()后不能join()
      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
      	cout << "我的线程执行" << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "我的线程执行完毕" << endl;
      }
      int main() {
      	thread th(test);
      	th.detach();
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      

      在这里插入图片描述

      主线程执行完毕后,子线程由运行时库接管,无法打印出来

    3. joinable()

      //joinable():判断是否可以成功join()或者detach(),返回true或者false
      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test() {
      	cout << "我的线程执行" << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "我的线程执行完毕" << endl;
      }
      int main() {
      	thread th(test);
      	if (th.joinable()) {
      		cout << "1:joinable()==true" << endl;
      	}
      	else {
      		cout << "1:joinable()==false" << endl;
      	}
      	th.detach();
      	if (th.joinable()) {
      		cout << "2:joinable()==true" << endl;
      	}
      	else {
      		cout << "2:joinable()==false" << endl;
      	}
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      

    在这里插入图片描述

  • 其他创建线程的方法

    1. 类对象作为可调用对象

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class Test {
      public:
      	void operator()() {//不能带参数
      		cout << "我的线程开始执行" << endl;
      		for (int i = 0; i < 10; ++i);
      		cout << "我的线程执行完毕" << endl;
      	}
      };
      
      int main() {
      	Test test;
      	thread th(test);
      	th.join();
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      

      若把join()改成detach(),主线程执行结束,test还在吗?

      如果对象不在了,线程还能继续执行吗?

      这个对象实际上是被复制到线程中去,执行完线程后,test会被销毁,但是复制的对象还在

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class Test {
      public:
      	Test(){
      		cout << "Test()构造函数被执行" << endl;
      	}
      	Test(const Test& test) {
      		cout << "拷贝构造函数执行" << endl;
      	}
      	~Test() {
      		cout << "析构函数执行" << endl;
      	}
      	void operator()() {//不能带参数
      		cout << "线程开始执行" << endl;
      		for (int i = 0; i < 10; ++i);
      		cout << "线程执行完毕" << endl;
      	}
      };
      
      int main() {
      	Test test;
      	thread th(test);
      	th.detach();
      	for (int i = 0; i < 10; ++i) {
      		cout << "主线程执行完毕" << i << endl;
      	}
      	return 0;
      }
      

      在这里插入图片描述

      将detach()改为join()后

    在这里插入图片描述

    1. 用lambda表达式

      #include<iostream>
      #include<thread>
      using namespace std;
      
      
      int main() {
      	auto test = [] {
      		cout << "线程开始执行" << endl;
      		for (int i = 0; i < 10; ++i);
      		cout << "线程执行完毕" << endl;
      	};
      	thread th(test);
      	th.join();
      	cout << "主线程执行完毕"  << endl;
      	return 0;
      }
      
  • 传递临时对象作为线程参数

    1. 可调用对象带有参数时

      #include<iostream>
      #include<thread>
      using namespace std;
      
      //传入的不是myNum的引用,指向地址不同,实际是值传递,即便主线程detach(),子线程使用num不会出问题
      //传入的指针依旧是指向muBuf的地址,所以detach后,子线程会出问题。可以将char*改为const string&
      //使用const string&也有问题,即:不知道mybuf不知道什么时候转换为string,如果主线程执行完毕后,还没转换完毕,就有问题
      void test(const int& num, char* buf) {
      	cout << "线程开始执行" << endl;
      	cout << num << endl;
      	cout << buf << endl;
      	cout << "线程执行完毕" << endl;
      }
      
      int main() {
      	int myNum = 1;
      	int& num = myNum;
      	char myBuf[] = "only for test!";
      	thread th(test, num, myBuf);
      	th.join();
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      
      // 生成一个临时string对象,绑定const string&
      //使用这个方法,就会让string对象在主线程执行完毕前构造
      //在创建线程的同时构造临时对象是可行的
      thread th(test,num,string(myBuf));
      

      总结(针对detach())

      (1)若传递int这种简单类型参数,建议都是值传递,不要用引用

      (2)如果传递类对象,避免隐式类型转换。全部都要在创建线程就构建临时对象,然后函数参数中用引用,否则系统还会构造一次

    2. 线程id的概念

      (1)每个线程(包括主线程)实际都对应着一个数字,并且数字都不同

      (2)线程id可以用c++标准库中的函数来获取,即:std::this_thread::get_id()

    3. 临时对象构造时机抓捕

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class A {
      public:
      	int num;
      	//类型转换构造函数,可以把一个int转换成一个类A对象
      	A(int num) :num(num) {
      		cout << "构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	A(const A& a) :num(a.num) {
      		cout << "拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	~A() {
      		cout << "析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      };
      
      void test(const A& a) {
      	cout << "线程test的参数地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
      	cout << "主线程id是" << std::this_thread::get_id() << endl;
      	int num = 2;
      	thread th(test, num);
      	th.join();
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      

      在这里插入图片描述

      A类对象在子线程中构造的,若改为detach()后,当主线程执行完毕后,还没构造,就出现问题了

      //在创建线程就构建临时对象
      thread th(test, A(num));
      

    在这里插入图片描述

  • 传递类对象、智能指针作为线程参数

    1. 线程参数为应用时修改值,不会影响主线程的值

      #include<iostream>
      #include<thread>
      using namespace std;
      
      class A {
      public:
      	mutable int num;//标注为const也能修改
      	//类型转换构造函数,可以把一个int转换成一个类A对象
      	A(int num) :num(num) {
      		cout << "构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	A(const A& a) :num(a.num) {
      		cout << "拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      	~A() {
      		cout << "析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
      	}
      };
      
      void test(const A& a) {
      	a.num = 199;//修改不会影响main的值
      	cout << "a.num=" << a.num << endl;
      	cout << "线程test的参数地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
      	cout << "主线程id是" << std::this_thread::get_id() << endl;
      	A a(10);
      	thread th(test, a);//将类对象作为线程参数
      	th.join();
      	cout << "主线程的a.num=" << a.num << endl;
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      

    在这里插入图片描述

    1. std::ref函数,可以让线程传入的参数不被复制(注:没使用detach())

      //将线程传入参数加上std::ref
      thread th(test, std::ref(a));
      

    在这里插入图片描述

    1. 传递智能指针

      #include<iostream>
      #include<thread>
      using namespace std;
      
      void test(unique_ptr<int> uptr) {
      	cout << "子线程id是" << std::this_thread::get_id() << endl;
      	cout << "当前智能指针的地址是" << uptr << endl;
      }
      
      int main() {
      	cout << "主线程id是" << std::this_thread::get_id() << endl;
      	unique_ptr<int> uptr(new int(10));
      	cout << "当前智能指针的地址为" << uptr << endl;
      	thread th(test, std::move(uptr));//将类对象作为线程参数
      	th.join();
      	cout << "主线程执行完毕" << endl;
      	return 0;
      }
      

    在这里插入图片描述

    若改为detach(),当主线程执行完毕后,uptr被释放,但子线程还在执行,此时就会出现问题

  • 用成员函数指针做线程函数

    #include<iostream>
    #include<thread>
    using namespace std;
    
    class A {
    public:
    	mutable int num;
    	//类型转换构造函数,可以把一个int转换成一个类A对象
    	A(int num) :num(num) {
    		cout << "构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
    	}
    	A(const A& a) :num(a.num) {
    		cout << "拷贝构造函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
    	}
    	~A() {
    		cout << "析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
    	}
    	void thread_work(int num) {
    		cout << "子线程thread_work执行了" << ",threadid=" << std::this_thread::get_id() << endl;
    	}
    };
    
    void test(const A& a) {
    	a.num = 199;//修改不会影响main的值
    	cout << "a.num=" << a.num << endl;
    	cout << "线程test的参数地址是" << &a << "threadid=" << std::this_thread::get_id() << endl;
    }
    
    int main() {
    	cout << "主线程id是" << std::this_thread::get_id() << endl;
    	A a(10);
        //第一个参数是对象函数指针,第二个参数对象,其后参数为函数所需参数
    	thread th(&A::thread_work, a, 1);
    	th.join();
    	cout << "主线程的a.num=" << a.num << endl;
    	cout << "主线程执行完毕" << endl;
    	return 0;
    }
    

c++11线程的互斥量

  • 创建和等待多个线程

    #include<iostream>
    #include<thread>
    #include<vector>
    using namespace std;
    
    
    //线程的入口函数
    void test(int num) {
    	cout << "线程开始执行,线程编号=" << num << endl;
    	for (int i = 0; i < 10; ++i);
    	cout << "线程执行结束了,线程编号=" << num << endl;
    }
    
    int main() {
    	cout << "主线程开始执行" << endl;
    	//创建和等待多个线程
    	vector<thread> threads;
    	//创建10个线程,线程入口统一使用test
    	for (int i = 0; i < 10; ++i) {
    		threads.push_back(thread(test, i));//创建10个线程,同时这10个线程开始执行
    	}
    	for (auto i = threads.begin(); i != threads.end(); ++i) {
    		i->join();//等待10个线程返回
    	}
    	cout << "主线程执行结束" << endl;
    	return 0;
    }
    

    在这里插入图片描述

    表明线程的执行是无序的,和操作系统内部对线程的调度有关

  • 数据共享问题分析——mutex

    1. 只读的数据

      #include<iostream>
      #include<thread>
      #include<vector>
      using namespace std;
      
      int global = 10;//共享数据
      
      //线程的入口函数
      void test(int num) {
      	cout << "编号为" << std::this_thread::get_id() << "打印global_v的值" << global << endl;
      	for (int i = 0; i < 10; ++i);
      	cout << "线程执行结束了,线程编号=" << std::this_thread::get_id() << endl;
      }
      
      int main() {
      	cout << "主线程开始执行" << endl;
      	//创建和等待多个线程
      	vector<thread> threads;
      	//创建10个线程,线程入口统一使用test
      	for (int i = 0; i < 10; ++i) {
      		threads.push_back(thread(test, i));//创建10个线程,同时这10个线程开始执行
      	}
      	for (auto i = threads.begin(); i != threads.end(); ++i) {
      		i->join();//等待10个线程返回
      	}
      	cout << "主线程执行结束" << endl;
      	return 0;
      }
      

    在这里插入图片描述

    所有线程读取的数据都相等,即:只读数据安全稳定

    1. 有读有写

      #include<iostream>
      #include<thread>
      using namespace std;
      
      int num[10] = { 0 };
      
      void write() {
      	for (int i = 0; i < 10; ++i) {
      		num[i] = 1;
      		this_thread::sleep_for(std::chrono::milliseconds(1));
      	}
      }
      
      void read() {
      	for (int i = 1; i < 10; ++i) {
      		if (num[i] != num[i - 1]) {
      			cout << "数据不一致" << endl;
      		}
      	}
      }
      
      int main() {
      	cout << "主线程开始执行" << endl;
      	//创建2个线程,一个线程负责读,一个负责写
      	thread write(write);
      	thread read(read);
      	write.join();
      	read.join();
      	cout << "主线程执行结束" << endl;
      	return 0;
      }
      

      在这里插入图片描述

  • 共享数据的保护

    1. 互斥量的基本概念

      类的对象,可以理解为一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程可以锁定成功(成功的标志是lock()函数返回),如果没有锁定成功,线程就会阻塞在这里,不断尝试加锁

    2. 互斥量的用法

      1. 先lock(),操作共享数据,unlock()

      2. lock()和unlock()要成对使用,lock()必然要有unlock,每调用一次lock(),必然要调用一次unlock()

      3. 注意:mutex是不可复制对象,所以mutex作为类的成员要注意。C++中thread调用“带mutex的类”的成员函数报错C2661:std::tuple解决方法_tomwillow的博客-CSDN博客

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      int num[10] = { 0 };
      
      void write() {
      	int copy[10];
      	for (int i = 0; i < 10; ++i) {
      		copy[i] = 1;
      		this_thread::sleep_for(std::chrono::milliseconds(1));
      	}
      	g_mutex.lock();
      	memcpy(num, copy, 10);
      	g_mutex.unlock();
      }
      
      void read() {
      	int copy[10] = { 0 };
      	g_mutex.lock();
      	memcpy(copy, num, 10);
      	g_mutex.unlock();
      	for (int i = 1; i < 10; ++i) {
      		if (copy[i] != copy[i - 1]) {
      			cout << "数据不一致" << endl;
      		}
      	}
      	
      }
      
      int main() {
      	cout << "主线程开始执行" << endl;
      	//创建2个线程,一个线程负责读,一个负责写
      	thread write(write);
      	thread read(read);
      	write.join();
      	read.join();
      	cout << "主线程执行结束" << endl;
      	return 0;
      }
      

    在这里插入图片描述

    1. 为了防止忘记unlock(),引入一个叫std::lock_guard的类模板。类似于智能指针

      (1)std::lock_guard直接取代了lock()和unlock()

      (2)lock_guard构造函数中执行了lock(),析构函数中执行了unlock()

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      int num[10] = { 0 };
      
      void write() {
      	int copy[10];
      	for (int i = 0; i < 10; ++i) {
      		copy[i] = 1;
      		this_thread::sleep_for(std::chrono::milliseconds(1));
      	}
      	std::lock_guard<mutex> write_mutex(g_mutex);
      	memcpy(num, copy, 10);
      }
      
      void read() {
      	int copy[10] = { 0 };
      	std::lock_guard<mutex> read_mutex(g_mutex);
      	memcpy(copy, num, 10);
      	for (int i = 1; i < 10; ++i) {
      		if (copy[i] != copy[i - 1]) {
      			cout << "数据不一致" << endl;
      		}
      	}
      	
      }
      
      int main() {
      	cout << "主线程开始执行" << endl;
      	//创建2个线程,一个线程负责读,一个负责写
      	thread write(write);
      	thread read(read);
      	write.join();
      	read.join();
      	cout << "主线程执行结束" << endl;
      	return 0;
      }
      

      如果不想线程执行完毕后std::lock_guard才执行析构函数,可以将其和需要加锁的共享数据放到{}中

      {
          std::lock_guard<mutex> write_mutex(g_mutex);
          memcpy(num, copy, 10);
      }
      //此时std::lock_guard属于{}这个作用域,当在{}外后,std::lock_guard生命周期结束,就会析构,即执行unlock
      
    2. 死锁

      1. 死锁的前提是至少有两个互斥量(锁头)才能产生

      2. 目前有两个互斥量,即:锁1,锁2,两个线程A,B

        (1)线程A执行的时候,这个线程先锁锁1,把锁1锁成功了,然后它去锁锁2,此时出现上下文切换,线程A让出CPU

        (2)线程B执行了,这个线程先锁锁2,因为锁2没有被锁,所以锁2锁成功,然后它去锁锁1

        (3)此时死锁产生,线程A拿不到锁2,解不开锁1,线程B拿不到锁1,解不开锁2,两个线程就无限互相等待下去

      3. 死锁的一般解决办法:保证两个互斥量的lock()顺序一致

      4. std::lock()函数模板

        1. 一次锁住两个或两个以上的互斥量(1个不行),它就不会存在多个线程因为锁的顺序导致的死锁风险。

        2. 如果互斥量中有一个没锁住,它就会一直等待,直到所有的互斥量都锁住

        3. 要么多个互斥量都锁住,要么都没有锁住。如果只锁一个其他没成功,它就会立即把已经lock()的都unlock()

          mutex g_mutex1;
          mutex g_mutex2;
          std::lock(g_mutex1,g_mutex2);
          g_mutex1.unlock();
          g_mutex2.unlock();
          
        4. std::lock_guard的std::adopt_lock

          //是一个结构体对象,其一个标记作用,表示互斥量已经lock()
          //std::adopt_lock让lock_guard不执行lock()
          mutex g_mutex1;
          mutex g_mutex2;
          std::lock(g_mutex1,g_mutex2);
          std::lock_guard<mutex> guard1(g_mutex1,std::adopt_lock);
          std::lock_guard<mutex> guard2(g_mutex2,std::adopt_lock);
          
      5. unique_lock

        (1)unique_lock是个类模板,和lock_guard类似,都是对mutex进行lock()和unlock()操作的

        (2)unique_lock比lock_guard更灵活,但效率上差一点,内存占用多一点。

        (3)用法和lock_guard类似

        mutex g_mutex;
        std::unique_lock<std::mutex> uniqueLock(g_mutex);
        

        (4)unique_lock第二个参数

        ​ a. std::adopt_lock(lock_guard也可使用):表示互斥量已经lock(),即:不需要在unique_lock的构造函数进行lock()

        ​ b. std::try_to_lock:会尝试mutex的lock()去锁定mutex,但如果没有锁定成功,就会立即返回,并不会阻塞(前提是使用try_to_lock前不能单独使用lock())

        std::unique_lock<mutex> uniqueLock(g_mutex,std::try_to_lock);
        if(uniqueLock.owns_lock()){//尝试Lock()成功
            
        }
        
        
        //实例
        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
        	int copy[10];
        	for (int i = 0; i < 10; ++i) {
        		copy[i] = 1;
        	}
        	unique_lock<mutex> uniqueLock(g_mutex);
        	this_thread::sleep_for(chrono::milliseconds(2000));
        	memcpy(num, copy, 10);
        }
        
        void read() {
        	int copy[10] = { 0 };
        	unique_lock<mutex> uniqueLock(g_mutex,std::try_to_lock);
        	if (uniqueLock.owns_lock()) {
        		memcpy(copy, num, 10);
        	}
        	else {
        		cout << "线程2没有拿到锁" << endl;
        	}
        	for (int i = 1; i < 10; ++i) {
        		if (copy[i] != copy[i - 1]) {
        			cout << "数据不一致" << endl;
        		}
        	}
        }
        
        int main() {
        	cout << "主线程开始执行" << endl;
        	//创建2个线程,一个线程负责读,一个负责写
        	thread write(write);
        	thread read(read);
        	write.join();
        	read.join();
        	cout << "主线程执行结束" << endl;
        	return 0;
        }
        

        在这里插入图片描述

        ​ c. std::defer_lock:并没有给mutex,即:初始化一个没有lock()的mutex(defer_lock的前提是不能先lock()),经常配合unique_lock的成员函数使用

      6. unique_lock的重要成员函数

        (1)lock()

        (2)unlock():unique_lock也可以自动解锁

        (3)try_lock():(类似于try_to_lock)尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,返回true,这个函数是不阻塞

        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
        	int copy[10];
        	for (int i = 0; i < 10; ++i) {
        		copy[i] = 1;
        	}
        	unique_lock<mutex> uniqueLock(g_mutex);
        	this_thread::sleep_for(chrono::milliseconds(2));
        	memcpy(num, copy, 10);
        }
        
        void read() {
        	int copy[10] = { 0 };
        	unique_lock<mutex> uniqueLock(g_mutex,defer_lock);
        	if (uniqueLock.try_lock() == true) {
        		memcpy(copy, num, 10);
        	}
        	else {
        		cout << "线程2没有拿到锁" << endl;
        	}
        	for (int i = 1; i < 10; ++i) {
        		if (copy[i] != copy[i - 1]) {
        			cout << "数据不一致" << endl;
        		}
        	}
        }
        
        int main() {
        	cout << "主线程开始执行" << endl;
        	//创建2个线程,一个线程负责读,一个负责写
        	thread write(write);
        	thread read(read);
        	write.join();
        	read.join();
        	cout << "主线程执行结束" << endl;
        	return 0;
        }
        

        在这里插入图片描述

        (4)release():返回它所管理的mutex对象指针,并释放所有权,也就是说,这个unique_lock和mutex不再联系(unlock不会释放所有权)。

        #include<iostream>
        #include<thread>
        #include<mutex>
        using namespace std;
        
        mutex g_mutex;
        int num[10] = { 0 };
        
        void write() {
        	int copy[10];
        	for (int i = 0; i < 10; ++i) {
        		copy[i] = 1;
        	}
        	unique_lock<mutex> uniqueLock(g_mutex);
        	mutex* ptx = uniqueLock.release();//释放mutex所有权,所以需要自己unlock()
        	memcpy(num, copy, 10);
        	ptx->unlock();
        }
        
        void read() {
        	int copy[10] = { 0 };
        	unique_lock<mutex> uniqueLock(g_mutex);
        	memcpy(copy, num, 10);
        	for (int i = 1; i < 10; ++i) {
        		if (copy[i] != copy[i - 1]) {
        			cout << "数据不一致" << endl;
        		}
        	}
        }
        
        int main() {
        	cout << "主线程开始执行" << endl;
        	//创建2个线程,一个线程负责读,一个负责写
        	thread write(write);
        	thread read(read);
        	write.join();
        	read.join();
        	cout << "主线程执行结束" << endl;
        	return 0;
        }
        
      7. unique_lock所有权

        mutex g_mutex;
        unique_lock<mutex> uniqueLock(g_mutex);//uniqueLock拥有g_mutex的所有权
        //不能复制所有权,但是所有权可以转移
        
        //转移方法1
        unique_lock<mutex> uniqueLock_(move(uniqueLock));//当前uniqueLock失去g_mutex控制权,指向空,uniqueLock_指向g_mutex
        
        //转移方法2
        //从函数返回一个局部的unique_lock对象是允许的
        //返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
        unique_lock<mutex> rtn_unique_lock(){
            unique_lock<mutex> uniqueLock(g_mutex);
            return uniqueLock;
        }
        
  • 单例设计模式共享数据分析、解决,call_once

    1. 单例设计模式

      (1)单例:整个项目中,有某个或者某些特殊的类,并且只能创建一个属于该类的对象

      (2)单例类示例

      #include<iostream>
      using namespace std;
      
      class MyCAS {//这是一个单例类
      private:
      	MyCAS(){}//私有化构造函数
      private:
      	static MyCAS* m_instance;//静态成员变量
      public:
      	static MyCAS* getInstance() {
      		if (m_instance == nullptr) {
      			m_instance = new MyCAS();
      			static Release release;//当程序退出时执行析构函数
      		}
      		return m_instance;
      	}
      	class Release {//用来释放对象
      	public:
      		~Release() {
      			if (MyCAS::m_instance!=nullptr) {
      				delete m_instance;
      				MyCAS::m_instance = nullptr;
      			}
      		}
      	};
      };
      //类静态变量初始化
      MyCAS* MyCAS::m_instance = nullptr;
      
      int main() {
      	MyCAS* ptr = MyCAS::getInstance();//创建一个对象,返回该类(对象)指针
      	MyCAS* ptr_ = MyCAS::getInstance();
      	cout << "ptr=" << ptr << endl;
      	cout << "ptr_=" << ptr_ << endl;
      	return 0;
      }
      

    在这里插入图片描述

    1. 单例设计模式共享数据问题分析、解决

      问题:在创建的线程(非主线程)中创建单例类对象,并且这种线程可能不止一个,就需要将getInstance()这种成员函数互斥

      原因:当第一个线程在new之前失去cpu,第二个线程就会执行new一次,此时第一个线程再次执行,就又会new一次,这就不符合单例设计模式了

      示例:

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      
      mutex g_mutex;
      
      class MyCAS {//这是一个单例类
      private:
      	MyCAS(){}//私有化构造函数
      private:
      	static MyCAS* m_instance;//静态成员变量
      public:
      	static MyCAS* getInstance() {
      		//提高效率,只有第一次没初始化时会加锁
      		if (m_instance == nullptr) {//双重锁定(双重检查)
      			unique_lock<mutex> lock(g_mutex);//自动加锁
      			if (m_instance == nullptr) {
      				m_instance = new MyCAS();
      				static Release release;//当程序退出时执行析构函数
      			}
      		}
      		return m_instance;
      	}
      
      	class Release {//用来释放对象
      	public:
      		~Release() {
      			if (MyCAS::m_instance!=nullptr) {
      				delete m_instance;
      				MyCAS::m_instance = nullptr;
      			}
      		}
      	};
      
      	void fun() {
      		cout << "测试" << endl;
      	}
      };
      //类静态变量初始化
      MyCAS* MyCAS::m_instance = nullptr;
      
      void thread1() {
      	cout << "线程th1开始执行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "线程th1执行完毕" << endl;
      }
      
      void thread2() {
      	cout << "线程th2开始执行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "线程th2执行完毕" << endl;
      }
      
      int main() {
      	thread th1(thread1);
      	thread th2(thread2);
      	th1.join();
      	th2.join();
      	return 0;
      }
      
    2. std::call_once()——c++11标准

      函数的第二个参数是一个函数名,需要和std::once_flag标记结合使用

      功能:保证函数只被调用一次,即:通过std::once_flag这个标记来判断函数是否执行,当调用call_once()成功后,call_once()就把这个标记设置为一种已调用状态。后续再次调用call_once(),只要once_falg被设置为已调用,就不会执行了

      具备互斥量的能力

      #include<iostream>
      #include<thread>
      #include <mutex>
      using namespace std;
      
      once_flag g_flag;//这是系统定义的标记
      
      class MyCAS {//这是一个单例类
      	static void CreateInstance() {//只被调用一次
      		m_instance = new MyCAS();
      		static Release release;//当程序退出时执行析构函数
      	}
      private:
      	MyCAS(){}//私有化构造函数
      private:
      	static MyCAS* m_instance;//静态成员变量
      public:
      	static MyCAS* getInstance() {
      		//两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕CreateInstance后,就会放弃执行CreateInstance
      		call_once(g_flag, CreateInstance);
      		return m_instance;
      	}
      
      	class Release {//用来释放对象
      	public:
      		~Release() {
      			if (MyCAS::m_instance!=nullptr) {
      				delete m_instance;
      				MyCAS::m_instance = nullptr;
      			}
      		}
      	};
      
      	void fun() {
      		cout << "测试" << endl;
      	}
      };
      //类静态变量初始化
      MyCAS* MyCAS::m_instance = nullptr;
      
      void thread1() {
      	cout << "线程th1开始执行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "线程th1执行完毕" << endl;
      }
      
      void thread2() {
      	cout << "线程th2开始执行" << endl;
      	MyCAS* ptr = MyCAS::getInstance();
      	cout << "线程th2执行完毕" << endl;
      }
      
      int main() {
      	thread th1(thread1);
      	thread th2(thread2);
      	th1.join();
      	th2.join();
      	return 0;
      }
      

      c++11线程的条件变量

  • 轮询机制:每隔一定时间,进行查询

    缺点:查询不能太频繁(浪费CPU),也不能太不频繁(缓冲区满),难以把握。性能不佳

    #include <iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    
    mutex g_mutex;
    int g_buf[100];//缓冲区,最多存放一百个数
    int g_count = 0;
    
    //第一个线程:生产者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一个1……20之间的随机数
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
            //存放一个物品(这里存放的数据代表物品)
            g_mutex.lock();
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            g_mutex.unlock();
        }
    }
    
    //第二个线程:消费者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));
            g_mutex.lock();
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
            g_mutex.unlock();
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    

    在这里插入图片描述

  • 条件变量——std::condition_variable

    (1)std::condition_variable实际是一个类,是一个和条件相关的一个类,即:等待一个条件达成。

    (2)这个类是需要和互斥量来配合工作,用的时候我们要生成这个类的对象

    (3)wait()

    ​ a. 如果第二个参数(可调用对象)返回的值是false,那么wait()将解锁互斥量,并堵塞到 本行。堵塞到其他某个线程调用notify_one()成员函数为止。如果第二个参数返回值是true,那么wait()会直接返回

    ​ b. 如果wait()没有第二个参数,那么就和第二个参数返回false效果一样。

    (4)notify_one()

    ​ (1)尝试把一个wait()的线程唤醒,如果没有wait()线程,那么notify_one()就没效果,但是不会阻塞在notify_one()这里

    ​ (2)当其他线程用notify_one()将wait()【原本阻塞】,wait()就开始恢复干活了,即

    ​ a. 不断尝试重新获取互斥量锁,如果获取不到,线程就阻塞在wait()这里等着获取锁。 b. 如果获取到就继续执行;获取到锁就执行lock()。

    ​ 如果wait有第二个参数,就判断这个参数返回值,如果返回值为false,wait()又会对互斥量解锁,并再次阻塞,等待notify_one()唤醒

    ​ 如果wait没有第二个参数,则线程继续执行

    (5)示例

    #include <iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    
    mutex g_mutex;
    condition_variable g_cond;//生成一个条件变量对象
    int g_buf[100];//缓冲区,最多存放一百个数
    int g_count = 0;
    
    //第一个线程:生产者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一个1……20之间的随机数
            this_thread::sleep_for(chrono::milliseconds(50 * r));//避免本线程notify_one()后比wait()先拿到锁,休息时间在50-1000毫秒之间
            //存放一个物品(这里存放的数据代表物品)
            unique_lock<mutex> lock(g_mutex);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            g_cond.notify_one();
        }
    }
    
    //第二个线程:消费者
    void Consume() {
        while (true) {
            unique_lock<mutex> lock(g_mutex);
            g_cond.wait(lock, [&] {//lambda为可调用对象
                if (g_count > 0)
                    return true;
                return false;
                });
            for (int i = 0; i < g_count; ++i) {
                cout << "消耗物品:" << g_buf[i] << endl;
            }
            g_count = 0;
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
  • 上述代码深入思考

    (1) 当删除this_thread::sleep_for(chrono::milliseconds(50 * r))后,放入和消耗就可能不会交替执行,因为notify_one()后,线程可能先于wait()拿到锁

在这里插入图片描述

(2)注意:notify_one()不一定起作用,但不会阻塞。因为执行notify_one()时,如果没有其他线程wait(),它就没有效果

  • notify_all()

    当程序中有多个线程使用wait()时,使用notify_one()只会在某时刻唤醒其中任意一个线程,另外其他线程依然在阻塞中,即:任意时刻只有一个wait()尝试拿锁,其他都在阻塞中

    使用notify_all()会将所有wait()的线程都唤醒,所有线程的wait()都会尝试拿锁

c++11 async、future、packaged_task、promise

  • std::asyc、std::future创建后台任务并返回值

    1. std::asyc:是一个函数模板,用来启动一个异步任务,启动一个异步任务后,它会返回一个std::future(类模板)对象

    2. 启动一个异步任务:就是自动创建一个线程并开始执行对应的线程入口函数,它返回一个std::future对象

    3. std::future对象里就含有线程入口函数返回的结果(线程返回的结果),可以通过调用future对象的成员函数get()来获取结果

    4. std::future:提供了一种访问异步操作结果的机制,即:这个结果可能无法马上到达,但是不久的将来,当线程执行完毕的时候,就可以拿到结果

    5. 示例

      #include<iostream>
      #include<future>
      using namespace std;
      
      int myThread() {//线程入口函数
      	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      	this_thread::sleep_for(chrono::milliseconds(5000));
      	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      	return 5;
      }
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	future<int> result = async(myThread);//创建一个线程并执行,但是主函数不会阻塞在这里,会继续向下执行
      	cout << "continue……!" << endl;
      	cout << result.get() << endl;//主函数会阻塞在这里,等待线程返回
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include<future>
      using namespace std;
      
      class Thread {
      public:
      	int myThread(int num) {//线程入口函数
      		cout << num << endl;
      		cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      		this_thread::sleep_for(chrono::milliseconds(5000));
      		cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      		return 5;
      	}
      };
      
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	Thread th;
      	future<int> result = async(&Thread::myThread,&th,12);//创建一个线程并执行,但是主函数不会阻塞在这里,会继续向下执行
      	cout << "continue……!" << endl;
      	cout << result.get() << endl;//主函数会阻塞在这里,等待线程返回
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
    6. 上述程序通过future对象的get()成员函数等待线程结束并返回结果,即:会阻塞future对象所在的线程,并返回结果

    7. future对象的get()成员函数只能调用一次

    8. future对象还有一个wait()成员函数,该函数只是等待线程结束,本身不会返回结果

    9. 额外向std::async()传递一个参数,该参数类型是std::launch类型(枚举类型),来达到一些特殊的目的

      (1)std::launch::deferred:不会创建新线程,通过future对象调用get()或者wait()函数,就会直接调用async中的可调用对象

      #include<iostream>
      #include<future>
      using namespace std;
      
      class Thread {
      public:
      	int myThread(int num) {//线程入口函数
      		cout << num << endl;
      		cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      		this_thread::sleep_for(chrono::milliseconds(5000));
      		cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      		return 5;
      	}
      };
      
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	Thread th;
      	future<int> result = async(launch::deferred, &Thread::myThread, &th, 12);
      	cout << "continue……!" << endl;
      	cout << result.get() << endl;
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      

      在这里插入图片描述

      (2)std::launch::async:强制这个异步任务在新线程上执行,即:系统必须创建出新线程执行可调用对象

      (3)std::launch::async | std::launch::deferred:这是async的默认值,表示调用async的行为可能是创建新线程并立即执行,或者没有创建新线程并延迟到调用get()或者wait()才开始执行任务入口函数

    10. std::async和std::thread的区别:

      (1)std::async不一定会创建新线程执行可调用对象。std::thread会创建新线程,如果系统资源紧张,创建线程失败,那么整个程序就会崩溃

      (2)std::async调用方法可以用简单的get()函数拿到线程可调用对象的返回值

      (3)std::thread创建的线程过多,可能创建失败,系统报告异常,崩溃。而std::async一般不会报告异常、崩溃,因为当系统资源紧张导致无法创建新线程的时候,std::async使用默认值调用时就不会创建新线程,而是后序当某个线程使用get()获取返回值时,就在该线程执行可调用对象

    11. std::async使用默认值

      当std::async使用默认值作为参数时,会产生不确定性【即:是否创建新线程】

      借助future的wait_for()来判断是否创建新线程

      #include<iostream>
      #include<thread>
      #include<atomic>
      #include<future>
      using namespace std;
      
      atomic<int> g_num = 0;
      
      int myThread() {
      	cout << "myThread() start, " << "thread_id=" << this_thread::get_id() << endl;
      	cout << "myThread() end, " << "thread_id=" << this_thread::get_id() << endl;
      	return 1;
      }
      
      int main() {
      	cout << "main() start, " << "thread_id=" << this_thread::get_id() << endl;
      	future<int> result = async(myThread);
      	future_status status = result.wait_for(0s);//等待chrono::seconds(0)
      	if (status == future_status::deferred) {
      		cout << "没有创建新线程" << endl;
      		cout << result.get() << endl;//此时才执行myThread()
      	}
      	else {
      		//创建了新线程
      		if (status == future_status::ready) {
      			cout << "线程成功创建" << endl;
      			cout << result.get() << endl;
      		}
      		else if (status == future_status::timeout) {
      			//超时,线程还没执行完毕
      			cout << "超时,线程还在执行中" << endl;
      			cout << result.get() << endl;
      		}
      	}
      	cout << "main() end, " << "thread_id=" << this_thread::get_id() << endl;
      	return 0;
      }
      
  • std::packaged_task

    1. std::packaged_task:是一个类模板,模板参数是各种可调用对象,通过std::packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数

    2. packaged_task:包装的对象还是可以直接调用的

    3. 可以通过get_future()获取future对象,从而取得线程的返回值

    4. 示例

      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int myThread(int num) {//线程入口函数
      	cout << num << endl;
      	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      	this_thread::sleep_for(chrono::milliseconds(5000));
      	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      	return 5;
      }
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc(myThread);//将函数myThread通过packaged_task包装起来
      	thread th(std::ref(myFunc), 1);
      	th.join();
      	std::future<int> result = myFunc.get_future();
      	cout << result.get() << endl;
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc([](int num) {
      			cout << num << endl;
      			cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      			this_thread::sleep_for(chrono::milliseconds(5000));
      			cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      			return 5;
      		});
      	thread th(ref(myFunc), 12);
      	th.join();
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include <future>
      #include<thread>
      using namespace std;
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc([](int num) {
      			cout << num << endl;
      			cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      			this_thread::sleep_for(chrono::milliseconds(5000));
      			cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      			return 5;
      		});
      	myFunc(12);//直接调用
      	future<int> result = myFunc.get_future();
      	cout << result.get() << endl;//注意,没有创建新线程,而是触发lambda表达式执行,即:相当于函数调用
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
      #include<iostream>
      #include <future>
      #include<thread>
      #include<vector>
      using namespace std;
      
      vector<packaged_task<int(int)>> vec;
      
      int main() {
      	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
      	packaged_task<int(int)> myFunc([](int num) {
      		cout << num << endl;
      		cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
      		this_thread::sleep_for(chrono::milliseconds(5000));
      		cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
      		return 5;
      		});
      	vec.push_back(move(myFunc));//这里用到了移动语义,此时myFunc为空
      	packaged_task<int(int)> myFunc_ = move(vec.back());
      	vec.pop_back();
      	myFunc_(123);
      	future<int> result = myFunc_.get_future();
      	cout << result.get() << endl;
      	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
      	return 0;
      }
      
  • std::promise

    std::promise也是一个类模板,能够在某个线程中给它赋值,然后可以在其他线程中,将这个值取出使用

    #include<iostream>
    #include<thread>
    #include <future>
    using namespace std;
    
    void myThread(promise<int>& temp, int num) {
    	//模拟完成一系列复杂操作
    	this_thread::sleep_for(chrono::milliseconds(5000));
    	temp.set_value(num);//将结果保存到promise对象中
    }
    
    int main() {
    	promise<int> myTemp;//声明一个promise对象,保存类型为int
    	thread th(myThread, ref(myTemp), 520);
    	th.join();
    	//获取结果值
    	future<int> result = myTemp.get_future();//promise和future绑定,用于获取线程返回值
    	cout << result.get() << endl;
    	return 0;
    }
    
    #include<iostream>
    #include<thread>
    #include <future>
    using namespace std;
    
    void myThread(promise<int>& temp, int num) {
    	//模拟完成一系列复杂操作
    	this_thread::sleep_for(chrono::milliseconds(5000));
    	temp.set_value(num);//将结果保存到promise对象中
    }
    
    void myThread_(future<int>& temp) {
    	int result = temp.get();
    	cout << "myThread_ result=" << result << endl;
    }
    
    int main() {
    	promise<int> myTemp;//声明一个promise对象,保存类型为int
    	thread th(myThread, ref(myTemp), 520);
    	th.join();
    	future<int> result = myTemp.get_future();//promise和future绑定,用于获取线程返回值
    	thread th_(myThread_, ref(result));
    	th_.join();
    	return 0;
    }
    

future其他成员函数、shared_future、atomic

  • std::future其它成员函数

    wait_for(time):阻塞time时长,若time时长后,线程还没返回,则wait_for返回future_status::timeout。如果在time时长之内,线程成功返回,则wait_for()返回future_status::ready。如果async第一个参数设置为std::launch::deferred,则wait_for()返回future_status::deferred

    #include<iostream>
    #include<future>
    using namespace std;
    
    int myThread() {//线程入口函数
    	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
    	this_thread::sleep_for(chrono::milliseconds(1000));
    	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
    	return 5;
    }
    
    int main() {
    	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
    	future<int> result = async(myThread);//创建一个线程并执行,但是主函数不会阻塞在这里,会继续向下执行
    	cout << "continue……!" << endl;
    	//枚举类型
    	future_status status = result.wait_for(chrono::seconds(2));
    	if (status == future_status::timeout) {
    		cout << "timeout" << endl;
    	}
    	else if (status == future_status::ready) {
    		cout << "成功返回" << endl;
    		cout << result.get() << endl;
    	}
    	else if (status == future_status::deferred) {
    		cout << "deferred" << endl;
    		cout << result.get() << endl;
    	}
    	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
    	return 0;
    }
    
    

在这里插入图片描述

  • std::shared_future

    std::future只能调用一次get()函数,因为get()函数的设计是一个移动语义

    std::shared_future:也是一个类模板,get()函数是复制数据

    #include<iostream>
    #include <future>
    #include<thread>
    using namespace std;
    
    int myThread() {//线程入口函数
    	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
    	this_thread::sleep_for(chrono::milliseconds(5000));
    	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
    	return 5;
    }
    
    void myThread_(shared_future<int>& temp) {
    	cout << "myThread_() start," << "threadid=" << this_thread::get_id() << endl;
    	cout << "myThread_():" << temp.get() << endl;
    	cout << "myThread_() end," << "threadid=" << this_thread::get_id() << endl;
    }
    
    int main() {
    	cout << "main() start," << "threadid=" << this_thread::get_id() << endl;
    	packaged_task<int(void)> myFunc(myThread);//将函数myThread通过packaged_task包装起来
    	thread th(std::ref(myFunc));
    	th.join();
    	shared_future<int> result = myFunc.get_future();
    	cout << "main():"<< result.get() << endl;
    	thread th_(myThread_, std::ref(result));
    	th_.join();
    	cout << "main() end," << "threadid=" << this_thread::get_id() << endl;
    	return 0;
    }
    

    在这里插入图片描述

  • std::atomic——原子操作

    原子操作概念及其范例

    (1)原子操作:是指“不可分割的操作”,要么执行成功,要么失败,即:是在多线程中不会打断的程序执行片段

    (2)原子操作:比互斥量的效率更胜一筹

    (3)互斥量加锁一般针对的是一个代码片段,而原子操作针对的一般都是一个变量,而不是一个代码片段

    (4)std::atomic:类模板,用来代表原子操作

    (5)示例

    #include<iostream>
    #include<thread>
    #include<atomic>
    using namespace std;
    
    atomic<int> g_num = 0;
    
    void Write() {
    	for (int i = 0; i < 1000000; ++i) {
            //可替换成g_num+=1;
    		++g_num;//操作为原子操作,在多线程中不会被打断
    	}
    }
    
    int main() {
    	thread th1(Write);
    	thread th2(Write);
    	th1.join();
    	th2.join();
    	cout<<"两个线程执行完毕,最终g_num="<<g_num<<endl;
    	return 0;
    }
    
    #include<iostream>
    #include<thread>
    #include<atomic>
    using namespace std;
    
    atomic<bool> g_flag = false;//线程退出标记
    
    void myThread() {
    	cout << "myThread() start," << "threadid=" << this_thread::get_id() << endl;
    	while (!g_flag) {
    		cout << "myThread() run," << "threadid=" << this_thread::get_id() << endl;
    		this_thread::sleep_for(chrono::milliseconds(1000));
    	}
    	cout << "myThread() end," << "threadid=" << this_thread::get_id() << endl;
    }
    
    int main() {
    	thread th1(myThread);
    	thread th2(myThread);
    	this_thread::sleep_for(chrono::milliseconds(2000));
    	g_flag = true;
    	th1.join();
    	th2.join();
    	return 0;
    }
    

    (6)atomic原子操作,针对++,--,+=,-=,*=,&=,!=是支持的,其它可能不支持

    #include<iostream>
    #include<thread>
    #include<atomic>
    using namespace std;
    
    atomic<int> g_num = 0;
    
    void Write() {
    	for (int i = 0; i < 1000000; ++i) {
    		g_num = g_num + 1;
    	}
    }
    
    int main() {
    	thread th1(Write);
    	thread th2(Write);
    	th1.join();
    	th2.join();
    	cout << "两个线程执行完毕,最终g_num=" << g_num << endl;
    	return 0;
    }
    

    在这里插入图片描述

    (7)load()函数:以原子方式读取atomic对象的值

    atomic<int> atm1;
    atomic<int> atm2=atm1;//尝试引用已删除的函数,拷贝赋值运算符也不让用
    atomic<int> atm3(atm1.load())
    

    (8)store()函数:以原子方式写入内容

    atomic<int> atm;
    atm.store(1);
    

windows临界区、其他各种mutex互斥量

  • windows临界区

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    #define WINDOWS
    
    #ifdef WINDOWS
        CRITICAL_SECTION winsec;//windows中的临界区,类似于mutex
    #endif
    
    mutex g_mutex;
    int g_buf[100];//缓冲区,最多存放一百个数
    int g_count = 0;
    
    //第一个线程:生产者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一个1……20之间的随机数
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
            //存放一个物品(这里存放的数据代表物品)
    #ifdef WINDOWS
            EnterCriticalSection(&winsec);//进入临界区,相当于mutex.lock()
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            LeaveCriticalSection(&winsec);//退出临界区,相当于mutex.unlock()
    #else
            g_mutex.lock();
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
            g_mutex.unlock();
    #endif // WINDOWS
        }
    }
    
    //第二个线程:消费者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//轮询
    #ifdef WINDOWS
            EnterCriticalSection(&winsec);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
            LeaveCriticalSection(&winsec);
    #else
            g_mutex.lock();
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
            g_mutex.unlock();
    #endif // WINDOWS
    
        }
    }
    
    int main()
    {
    #ifdef WINDOWS
        InitializeCriticalSection(&winsec);//使用临界区之前初始化
    #endif // WINDOWS
    
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
  • 多次进入临界区试验

    (1)在同一个线程中,windows中相同临界区变量代表的临界区的进入(EnterCriticalSection)可以被多次调用,但是EnterCriticalSection的调用次数需要和LeaveCriticalSection的调用次数要相等(否则,该线程始终都在临界区中,该线程一直执行,另一个线程阻塞)

    (2)c++11中mutex不允许lock多次

  • 自动析构技术

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    #define WINDOWS
    
    //用于自动释放windows下的临界区,防止忘记LeaveCriticalSection
    //类似与std::lock_guard
    //RAII类(Resource Acquisition initialization):资源获取即初始化
    class cWinLock {
    public:
        cWinLock(CRITICAL_SECTION* ptr) :ptr(ptr) {
            EnterCriticalSection(ptr);
        }
        ~cWinLock() {
            LeaveCriticalSection(ptr);
        }
    private:
        CRITICAL_SECTION* ptr;
    };
    
    #ifdef WINDOWS
        CRITICAL_SECTION winsec;//windows中的临界区,类似于mutex
    #endif
    
    mutex g_mutex;
    int g_buf[100];//缓冲区,最多存放一百个数
    int g_count = 0;
    
    //第一个线程:生产者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一个1……20之间的随机数
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
            //存放一个物品(这里存放的数据代表物品)
    #ifdef WINDOWS
            cWinLock wLock(&winsec);
            cWinLock wLock_(&winsec);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
    #else
            lock_guard<mutex> lock(g_mutex);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
    #endif // WINDOWS
        }
    }
    
    //第二个线程:消费者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//轮询
    #ifdef WINDOWS
            cWinLock wLock(&winsec);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
    #else
            lock_guard<mutex> lock(g_mutex);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
    #endif // WINDOWS
    
        }
    }
    
    int main()
    {
    #ifdef WINDOWS
        InitializeCriticalSection(&winsec);//使用临界区之前初始化
    #endif // WINDOWS
    
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
  • recursive_mutex递归的独占互斥量

    std::mutex:独占互斥量,在本线程lock后,本线程无法继续lock【除非unlock后】,其它线程也无法lock

    std::recursive_mutex:递归的独占互斥量,允许同一个线程中同一个互斥量多次被lock【第一次lock后没有unlock】,效率上比mutex低

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    recursive_mutex g_mutex;
    int g_buf[100];//缓冲区,最多存放一百个数
    int g_count = 0;
    
    //第一个线程:生产者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一个1……20之间的随机数
            this_thread::sleep_for(chrono::milliseconds(50 * r));//休息时间在50-1000毫秒之间
            //存放一个物品(这里存放的数据代表物品)
            lock_guard<recursive_mutex> lock(g_mutex);
            lock_guard<recursive_mutex> lock_(g_mutex);
            g_buf[g_count] = r;
            ++g_count;
            cout << "放入物品:" << r << endl;
        }
    }
    
    //第二个线程:消费者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//轮询
            lock_guard<recursive_mutex> lock(g_mutex);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
    
  • 带超时的互斥量std::timed_mutex和std::recursive_timed_mutex

    1. std::timed_mutex:带超时功能的独占互斥量

      (1)try_lock_for():参数是一段时间,等待一段时间,如果等待超时或者lock成功,继续流程

      #include <iostream>
      #include<thread>
      #include<mutex>
      #include<Windows.h>
      using namespace std;
      
      timed_mutex g_mutex;//带超时功能的独占互斥量
      int g_buf[100];//缓冲区,最多存放一百个数
      int g_count = 0;
      
      //第一个线程:生产者
      void Producer() {
          while (true) {
              int r = rand() % 20 + 1;//生成一个1……20之间的随机数
              //存放一个物品(这里存放的数据代表物品)
              if (g_mutex.try_lock_for(10ms)) {//等待100毫秒尝试lock
                  //在100毫秒内lock成功
                  g_buf[g_count] = r;
                  ++g_count;
                  cout << "放入物品:" << r << endl;
                  g_mutex.unlock();
              }
              else {
                  //没有在100毫秒内lock成功
                  cout << "Producer lock失败" << endl;
                  this_thread::sleep_for(chrono::microseconds(100));
              }
          }
      }
      
      //第二个线程:消费者
      void Consume() {
          while (true) {
              this_thread::sleep_for(chrono::milliseconds(50));//轮询
              lock_guard<timed_mutex> lock(g_mutex);
              if (g_count > 0) {
                  for (int i = 0; i < g_count; ++i) {
                      cout << "消耗物品:" << g_buf[i] << endl;
                  }
                  g_count = 0;
              }
          }
      }
      
      int main()
      {
          srand(time(nullptr));
          thread producer(Producer);
          thread consume(Consume);
          producer.join();
          consume.join();
          return 0;
      }
      

    在这里插入图片描述

    (2)try_lock_until:参数是一个未来时间点,在这个未来时间没到的时间内,不管lock是否成功,流程继续

    #include <iostream>
    #include<thread>
    #include<mutex>
    #include<Windows.h>
    using namespace std;
    
    timed_mutex g_mutex;//带超时功能的独占互斥量
    int g_buf[100];//缓冲区,最多存放一百个数
    int g_count = 0;
    
    //第一个线程:生产者
    void Producer() {
        while (true) {
            int r = rand() % 20 + 1;//生成一个1……20之间的随机数
            //存放一个物品(这里存放的数据代表物品)
            if (g_mutex.try_lock_until(chrono::steady_clock::now() + 100ms)) {//当前时间加上100ms
                //在以当前时刻算起的100ms内,lock成功
                g_buf[g_count] = r;
                ++g_count;
                cout << "放入物品:" << r << endl;
                g_mutex.unlock();
            }
            else {
                cout << "Producer lock失败" << endl;
                this_thread::sleep_for(chrono::microseconds(100));
            }
        }
    }
    
    //第二个线程:消费者
    void Consume() {
        while (true) {
            this_thread::sleep_for(chrono::milliseconds(50));//轮询
            lock_guard<timed_mutex> lock(g_mutex);
            if (g_count > 0) {
                for (int i = 0; i < g_count; ++i) {
                    cout << "消耗物品:" << g_buf[i] << endl;
                }
                g_count = 0;
            }
        }
    }
    
    int main()
    {
        srand(time(nullptr));
        thread producer(Producer);
        thread consume(Consume);
        producer.join();
        consume.join();
        return 0;
    }
    
    
    1. std::recursive_timed:带超时功能的递归独占互斥量
内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/dengrui/p/13869229.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!