C++中的线程

C++中的线程

以前的学习笔记,有一点乱

 #include<thread>
 #include<iostream>
 
 void myThread(void){
     std::cout << "I Love China!" << std::endl;
 }
 
 int main() {
     std::thread(myThread).join(); //主线程阻塞于此,子线程执行完毕后返回
     std::cout << "main thread" << std::endl;
     std::thread(myThread).detach();
 }
 /*
 join将阻塞子线程,让主线程等待子线程执行完毕,然后两线程汇合。
 若不使用join,可能导致主线程在子线程执行完之前接受而导致异常。
 主线程应当等待所有子线程执行完毕后再返回
 detach:主线程和子线程分离,主线程先执行结束不会影响子线程的执行,主线程并不阻塞等待
 子线程会失去与主线程的联系,线程结束后由运行时库清除资源
 主线程return后进程结束,被分离的子线程将无法打印信息到标准输出
 */
  • 若有以下代码:

    void aThread() {
        for (;;){
    		/*do something*/
        }
    }
    void anotherThread() {
        for (;;){
       		/*do something*/
        }
    }
    int main(){
        std::thread(aTread).join();//因为线程函数是无限循环,主线程阻塞于此
        std::thread(anotherThread).join();//该线程将没有机会执行
    }
    //使用临时变量(即不指定对象名)创建thread并执行且调用join()将使得主线程阻塞等待子线程结束,若为无限循环,将永远等待,后面的代码将没有机会执行,即后面一个线程将不会执行
    
    //正确的做法:
    std::thread t1(aThread); //开始执行
    std::thread t2(anotherThread);//开始执行
    t1.join();//主线程阻塞于此,若t1先于t2结束,主线程将继续等待t2,若反,继续等待t1,实际上也达到了等待两个线程结束的目的
    t2.join();
    //然而若后面还有语句,将不会被执行,因为主线程等待两个不可能结束的线程
    
  • joinable()函数:判断是否可以使用join或者detach,返回true或false;

  • 其他创建线程的方法

    1.用类,以及一个问题范例

    #include<thread>
    #include<iostream>
    
    class TA{
      public:
        TA(int& i):i(i){}
        void operator()(){  //必须有仿函数,不能带参数
            std::cout << "thread operator() excute" << std::endl;
            std::cout << "i: " << i << std::endl;
    		std::cout << "thread operator() exit" << std::endl;
        }
        int& i;
    };
    
    int main(){
        int i = 4;
        TA ta(i);				//类为可调用对象	
        std::thread(ta).join();
    }
    /*
    若使用detach,主线程可能先执行完毕,而子线程将使用主线程中创建的变量i的引用,产生不可预料的后果。
    另外,主线程结束后,ta对象将被销毁,但是子线程使用的是该对象的副本(可以创建拷贝构造函数验证),所以子线程依然正常执行。(类中不要使用指针、引用等)
    子线程执行完成后,对象将执行析构函数,主函数return后也将执行对象的析构函数
    */
    
    

    2.用lambda表达式

    #include<thread>
    #include<iostream>
    
    auto myLambdaThread = []{
        std::cout << "thread excute" << std::endl;
        std::cout << "thread exit" << std::endl;
    }; //分号
    
    int main() {
    	std::thread(myLambdaThread).join();
    }
    
    • 要避免的陷阱

      1. 在使用detach情形下,若线程函数使用了按引用传递的main函数中的变量,其仍是安全的,但并不推荐

      2. 在使用detach情形下,若线程函数使用了main函数中的指针,主线程结束后各变量将被销毁从而产生问题,故不能给线程函数传递指针

        void myThread(const int& i, const char* str){
            std::cout << "i:" << i << " string:" << str << std::endl;
        }
        
        int main(){
            char* str = "something";
            int i = 4;
            std::thread(myThread,i,str).detach(); //i实际上是按值传递(即使是按引用传递,其实质仍是按值传递,若有copy ctor,会调用之)
        }
        //若将const char*形参替换为const string&,存在的问题是系统在隐式地将char*转换为string时,main线程可能已经结束,非法操作
        //安全的做法
        void myThread(const int i, const string& str){...}
        std::thread(myThread,i,string(str));
        //既然还是要创建临时对象,为什么不放弃按引用传递而使用按值传递?
        

        在创建线程的同时构造临时对象的方法传递参数是可行的

    • 总结

      1. 若传递int这种简单的类型参数,建议使用值传递
      2. 如果传递类对象,避免隐式类型转换,全部在创建线程时构建临时对象,然后在 线程函数里用引用作为形参,否则(即按值)会多进行一次copy ctor。(若不使用临时对象,类实际上是在子线程中使用copy ctor构造的)
      3. 建议不使用detach,这样就不存在局部变量失效导致内存对内存的非法引用问题
class T {
public:
    T(std::string& str):str(str){}
    std::string str;
};

void mythread(const T& t) {//由于thread的参数皆为右值引用,故此处需要const关键字,另外,因为传入的十一个临时对象,对其的更改是无意义的,故声明为const
    std::cout << t.str << std::endl << std::this_thread::get_id;
}
//实质上还是按值传递
int main()
{
    std::string str("fsdafas");
    T t(str);
    std::thread(mythread,t).join();
    std::cout << "Hello World!\n";
}
/*
可以看到。无论是按引用还是按值传递,我们都无法在线程中对main函数中的变量做出改变
*/

关于右值引用的补充

  • 只能绑定到临时对象,多引用的对象将要销毁或没有其他用户使用该对象
  • 使用右值引用的代码可以自由的接管所引用对象的内容
  • 右值引用也是左值,用常量表达式初始化之后(必须的),可以用左值修改右值引用所引用临时对象的值,即可以用一个右值引用赋值给另一个右值引用

若需要在线程中修改局部变量的值

  1. 使用std::ref()

  2. 传递指针(智能指针)

  3. #include <iostream>
    #include<thread>
    class T {
    public:
        int i;
        T(int i) :i(i) {}
    };
    void mythread(T& t) {
        ++t.i;
    }
    int main() {
        std::string str("fsdafas");
        T t(0);
        std::cout << "before thread, i = " << t.i << std::endl;
        std::thread(mythread, std::ref(t)).join();//std::ref()
        std::cout << "After thread, i = " << t.i << std::endl;
    }
    
  4. #include <iostream>
    #include<thread>
    class T {
    public:
        int i;
        T(int i) :i(i) {}
    };
    void mythread(std::shared_ptr<T> pt) {
        ++pt->i;
    }
    int main() {
        auto ptr = std::make_shared<T>(0);
        std::cout << "before thread, i = " << ptr->i << std::endl;
        std::thread(mythread, ptr).join();
        std::cout << "After thread, i = " << ptr->i << std::endl;
    }
    /*
    若为unique_ptr,传参数时需要用std::move(ptr) 但是后面对ptr的使用是非法的
    */
    
线程id
  • std::this_thread::get_id();

创建等待多个线程

#include<iostream>
#include<thread>
#include<vector>

void mythread() {
    std::cout << "thred id: " << std::this_thread::get_id << std::endl;
}
int main() {
    std::vector<std::thread> vthrd;
    for (int i = 0; i < 10; ++i)
        vthrd.push_back(std::thread(mythread));
    for (auto& i : vthrd)
        i.join();
}

数据共享问题

  1. 只读的数据是安全稳定的,不需要什么特别的手段

  2. 有读有写的数据

  3. std::mutex mtx;
    std::mutex mtx1; //声明一把锁
    mtx.lock();
    mtx.unlock();
    
    std::lock(mtx,mtx1); //同时获得两(多)把锁,保证不会出现死锁问题:要么获得两把锁,要么都不获得,但是要自己调用unlock()
    std::lock_guard<std::mutex> guard(mtx, std::adopt_lock);
    std::lock_guard<std::mutex> guard(mtx1, std::adopt_lock);
    //配合第六行使用,在离开作用域时自动解锁不用手动调用unlock(),可以用大括号指明作用域,在大括号内声明lock_guard类(千万不要忘记对象的名字,否则将是立马被销毁的临时对象,锁将立马解开),然后对共享数据进行读写。若不指定adopt_lock,则guard会尝试加锁
    
单例设计模式:(使用频率高)

​ 在整个项目中,有某个/些特殊的类只能创建一个

实例:

class MyCAS{
private:
    MyCAS(){}
    static MyCAS *instance;//静态成员变量
public:
    class GabCollector{
    public: //不要忘记public关键字,否则无法调用其析构函数
      ~GabCollector(){
          if (instance){
              delete(instance);
              instance = nullptr;
          }
      }  
    };
  //MyCAS(const MyCAS&) = delete;
    static MyCAS* getInstance(){
        if (instance == nullptr){//初始化之后每次调用该函数都直接返回指向对象的指针
             std::lock_guard<std::mutex> guard(mtx);//保证只被某一个线程初始化一次
			if (instance == nullptr){
            	instance = new MyCAS();  //多个线程可能同时执行这一语句,会new出多块空间
            	static class GabCollector gc;//静态的,程序结束时释放且调用其析构函数
       		}
    	return instance;//虽然最终静态的instance只能指向一块内存,但两个线程执行该函数而返回的指针可能不一致
	}
    void test(){
        std::cout << "testing" << std::endl;
    }
};
MyCAS::instance = nullptr ;//在类外初始化静态变量

//main函数中
MyCAS* p = MyCAS::getInstance();//获得该对象


//由于instance是堆上分配的内存,必须手动释放,不能在mycas类析构函数释放内存
//在多线程中,
std::call_once():
  • 其第二个参数是一个函数名,保证函数a()只被调用一次,具备互斥量的能力,而且效率上比互斥量消耗更少

  • call_once()需要配合标记std::once_flag的对象使用

  • call_once就是根据std::once_flag来判断是否调用,一旦成功调用,其将被置为已调用状态,后续再调用a()将不再执行(改写单例模式中的代码)

    //单例设计模式
    std::once_flag flag;  //可以定义为成员变量
    class Cas {
    public:
    	static Cas* getInstance() { //为什么定义为static?:因为构造函数被设为私有,我们不能用正常的方式创建实例,而static成员函数是属于类而非具体的对象的,我们可以通过作用域运算符执行Cas::getInstance()函数从而创建一个单例对象
    		std::call_once(flag, createInstance);
    		return instance;
    	}
    private:
    	Cas() {}
    	static Cas* instance;
        /*其他成员*/
    	class Gc {
    	public:          //不要忘记写public,否则无法创建全局类Gc
    		~Gc() {
    			if (instance) {
    				delete(instance);
    				instance = nullptr;
    			}
    		}
    	};
    	static void createInstance() {
    		instance = new Cas;//可能还要初始化其他变量
    		static Gc gc; //程序结束时将调用其析构函数从而释放instance指针
    	}
    };
    
    Cas* Cas::instance = nullptr; //不要忘记在类外初始化静态成员变量以及初始化的格式
    
    int main() {
    	Cas* p = Cas::getInstance();
    	Cas* p1 = Cas::getInstance();
    	std::cout << "p = " << p << std::endl
    		<< "p1= " << p1 << std::endl;
    }
    //建议在主线程中创建单例对象,而在子线程中使用
    

条件变量 std::condition_variable类、wait()、notify_one

是一个和条件相关的类(非模板类),说白了就是等待一个条件达成,需要和互斥量配合使用

先定义一个unique_lock(并lock),再定义std::condition_variable类的对象之后,调用wait(),其第一个参数是unique_lock,第二个参数是可调用对象(函数,仿函数,lambda表达式),**如果第二个参数返回值是false,那么wait函数将解锁互斥量并阻塞等待,直到其他线程调用notify_one并且判断第二个参数返回true,将再次尝试加锁并返回。**如果wait()没有第二个参数,那么就把第二个参数当成false(即直接解锁互斥量并阻塞),当被notify唤醒时直接尝试加锁并返回:(请注意有第二个参数和没有第二个参数的区别,前者相当于有两个判断条件)

std::mutex mtx;
std::condition_variable cond;

std::unique_lock<std::mutex> guard(mtx);//先锁定互斥量
cond.wait(guard,[this]{ //若无此lambda表达式,视为false
    if (!msgQueue.empty())
        return false; //返回false,解锁互斥量并阻塞直到notify_one的调用,将再次尝试加锁(可能阻塞等待)
    return true;//返回true,wait()返回(并不解锁互斥量)
});

具体实例:

std::vector<int> vec;
std::mutex mtx;

std::condition_variable cond; //全局

void test1() {
	int temp;
	for (;;) {
		{//大括号定义unique_lock的作用域
			std::unique_lock<std::mutex> guard(mtx);//先加锁
			cond.wait(guard, [] { //若被通知唤醒然而第二个参数依然返回false,则继续等待
				return !vec.empty();//vec为空(返回false)时将阻塞
				}); //执行完此句将尝试获得锁(可能阻塞),获得后返回,往下执行
			temp = vec.back();
			vec.pop_back();
		}//释放锁
		std::cout << "Data " << temp << " popped" << std::endl;
	} //end for
}

void test2() {
	srand(time(NULL));
	int temp;
	for (;;) {
		temp = 1+rand() % 10;
		{//guard作用域
			std::unique_lock<std::mutex> guard(mtx);//加锁
			vec.push_back(temp);
		}//解锁
		std::cout << "Data " << temp << " pushed" << std::endl;
		cond.notify_one(); //唤醒线程,此句可以放在guard作用域里面
		Sleep(800);        //如果对端线程并未阻塞于wait(),此次notify可能无效
	}
}

int main() {
	std::thread a(test1);
	std::thread b(test2);
	a.join();
	b.join();
}
//vec中的数据可能越来越多,而来不及取出
//当wait函数没有第二个参数时,只有在有线程发出notify信号才会唤醒
//当第二个参数返回true时,不需要notify即可唤醒
//当第二个参数返回false时,即使其他线程调用notify,本线程仍然阻塞
//故不阻塞的条件是:线程被notify时,第二个参数即谓词返回true
使用unique_lock取代lock_guard(皆是类模板):
  1. 前者比后者灵活很多,但是效率低,资源消耗大

  2. 同lock_guard一样,构造时加锁,析构时解锁

  3. 第一个参数是互斥锁,可选的第二个参数:

  4. 前者可以临时解锁和加锁,减少了粒度,而后者必须离开作用域析构而解锁

  5. 前者可以有lock,unlock,try_lock,release等成员函数,后者没有

  6. 前者可以移动, 不可复制,后者不可以移动,不能复制

    std::unique_lock<std::mutex> guard(mtx);
    auto guard2 = guard; //error
    auto guard2(guard);
    auto guard3 = std::move(guard); //ok
    
    //示例
    std::mutex mtx,mtx1;
    std::lock(mtx,mtx1); 
    std::unique_lock<std::mutex> guard(mtx,std::adopt_lock);  //不要忘记对象名
    //(1)
    std::adopt_lock  //表示传入的互斥量已经被lock(使用mtx.lock(),或lock(mtx1,mtx2)),不需要在unique_lock构造时加锁
    //(2)
    std::try_to_lock //尝试用锁定互斥量,如果没能锁定成功,会立即返回,并不会阻塞。注意:调用之前不得锁住互斥量,否则会造成死锁,判断是否拿锁成功:
        if (guard.owns_lock()){
            /*操作共享数据*/
        } else {
            /*其他操作*/
        }
    //(3)
    std::defer_lock //表示并没有给传入的mtx加锁,后面在进入临界区需要自行加锁,在离开unique_lock的作用域时,(如果未解锁)它仍然析构而释放锁。
    std::unique_lock<std::mutex> guard(mtx,std::defer_lock); //此时并未给mtx上锁 defer意味延后
    guard.lock();  //注意此处执行的是unique_lock的成员函数
    /*处理共享数据*/
    guard.unlock();  //手动unlock是为了临时解锁
    /*处理非共享数据*/
    guard.lock();  //注意此处执行的是unique_lock的成员函数
    /*处理共享数据*/
    //离开作用域自动解锁(如果没有手动解锁)
    
    //另一个成员函数
    guard.try_lock() //返回true表示拿到了锁,反之则反
    //另一个成员函数
    guard.release();//返回unique_lock对象管理的mutex指针并释放所有权,两者不再有关系,如果释放之前已经加锁,则我们有责任为之解锁
    
    std::unique<std::mutex> guard(mtx);
    std::mutex* pmtx = guard.release();
    /*处理共享数据*/
    pmtx->unlock();
    

    示例(2)

    std::mutex mtx;
    std::vector<int> v;
    
    void in() {
        int temp;
        for (;;) {
            std::unique_lock<std::mutex> guard(mtx, std::try_to_lock);
            if (guard.owns_lock()) {
                if (!v.empty()) {
                    temp = v.back();
                    v.pop_back();
                    std::cout << "RRRRR " << temp << " poped" << std::endl;
                }
            }
            else {
                std::cout << "RRRRR waiting" << std::endl;
                Sleep(1000);
            }
        }
    }
    
    void out() {
        for (;;) {
            {
                std::unique_lock<std::mutex> guard(mtx);
                v.push_back(4);
                std::cout << "WWWWW data 4 pushed" << std::endl;
            }
            Sleep(3000); 
        }
    }
    
    int main()
    {
        std::thread t1(out);
        std::thread t2(in);
        t1.join();
        t2.join();
    }
    
  7. unique_lock所有权的传递 (指的是mutex的所有权)

    unique_lock对象可以把自己对mutex的所有权转移给其他的unique_lock(不能复制,同unique_ptr一样,可以移动,不能赋值)

    std::unique_lock<std::mutex> guard(mtx);
    auto guard3 = std::move(guard); //or auto guard3(std::move(guard));
    //guard解除了与mtx互斥量的绑定,后者现在属于guard3 (继承了lock或unlock状态)
    

    可以返回一个unique_lock:

    std::unique_lock<std::mutex> rtnUniqueLock(std::mutex mtx) {
    	unique_lock<std::mutex> temp(mtx);
    	return temp;
    }
    //类比unique_ptr,从函数返回一个局部对象会导致系统生成一个临时的unique_lock对象,并调用其移动构造函数
    
std::async、std::future创建后台并返回值
  1. std::async是一个函数模板,用来启动一个异步任务,启动之后,它返回一个std::future对象(类模板)

  2. 异步任务:就是自动创建一个线程并开始执行对应线程入口函数并返回一个std::future对象,此对象中保存有线程入口函数的返回值,我们通过future对象的get函数来获取该返回值

  3. 提供了访问异步操作结果的机制,此结果可能没有办法马上拿到,但是线程执行完毕时就能拿到

    示例:

    #include<future>//包含该头文件
    int returnInt(int i) {
    	std::cout << "thread ID: " << std::this_thread::get_id() << std::endl;
    	std::chrono::milliseconds dura(i*1000); //定义i秒时间
    	std::this_thread::sleep_for(dura);//休息该时长
    	return 42;
    }
    int main()
    {
    	std::cout << "main thread ID: " << std::this_thread::get_id() << std::endl;
    	std::future<int> threadResult = std::async(returnInt,5);//将启动线程并返回线程指向结果,线程不会阻塞于此
    	std::cout << "main thread get " << threadResult.get() << std::endl;	//但会阻塞于get函数直至返回
    }
    //future与线程绑定
    //threadResult.wait() 将等待线程返回,但并不返回值
    //get函数只能调用一次
    //async的线程入口是可调用对象:函数、函数指针、重载了调用运算符的类、lambda表达式:
    class thrd{
    public:
        int operator()(int i) {
        	std::chrono::milliseconds dura(i*1000);
        	std::this_thread::sleep_for(dura);
        	return 42;
    	}
    };
    thrd t; //创建一个类对象,不能直接传入类
    auto res = std::async(t,3); //std::future<int> res
    std::cout << res.get();
    

    我们可以额外向std::async()传递一个参数(枚举类型)来达到一些特殊的目的:

    • std::launch::async,在调用函数时就开始创建线程,默认用此标记,可以省略

    • std::launch::defered :不立即开始线程,而是延迟到std::future的wait()或者get()时才开始(如果不调用这两个函数,则线程不会被创建),然而,即使是调用 了此二函数,也是在主线程中执行的线程入口函数:

      auto result = std::async(std::launch::deferred,a,3); //不会立刻调用线程入口函数
      result.get()//result.wait();  实际上在主线程中调用
      

std::packaged_task:把任务包装起来
  1. 是个类模板,它的模板参数是各种可调用对象。通过其把各种可调用对象(返回值类型和参数类型皆相同的)包装起来,方便未来作为线程入口函数;

  2. 示例:

    int thrd(int i) {
    	std::chrono::milliseconds dura(i * 1000);
    	std::this_thread::sleep_for(dura);
    	std::cout << std::this_thread::get_id << std::endl;
    	return 42;
    }
    
    int main() {
    	std::cout << std::this_thread::get_id << std::endl;
    	std::packaged_task<int(int)> mypt(thrd);//int(int)表示返回值和参数皆是int
    	std::thread t(std::ref(mypt), 1);
    	t.join();
    	std::future<int> res = mypt.get_future();//得到与mypt绑定的future
    	std::cout << "get result " << res.get();//通过future得到线程的返回结果
    }
    //包装lambda表达式
    int main() {
    	std::cout << std::this_thread::get_id << std::endl;
    	std::packaged_task<int(int)> mypt([](int i) {
    		std::chrono::milliseconds dura(i * 1000);
    		std::this_thread::sleep_for(dura);
    		std::cout << std::this_thread::get_id << std::endl;
    		return 42;
    		});//int(int)表示返回值和参数皆是int
    	std::thread t(std::ref(mypt), 1);
    	t.join();
    	std::future<int> res = mypt.get_future();//得到与mypt绑定的future
    	std::cout << "get result " << res.get();//通过future得到线程的返回结果
    }
    //mypt可以直接调用
    mypt(1); //不创建新线程,相当于程序调用
    std::future<int> res = mypt.get_future();
    res.get();
    //packaged_task对象可以装入容器
    std::vector<std::packaged_task<int(int)>> tasks;
    tasks.push_back(std::move(mypt));//  mypt现在为空
    auto iter = tasks.begin();
    auto pt = std::move(*iter);
    tasks.erase(iter);
    pt(1);
    
    std::promise
    1. 能够在某个线程中给他赋值,然后在其他线程中拿出来用(利用future类模板)

      void thrd(std::promise<int>& temp, int calc) { //参数一定要设置为引用
      	++calc;
      	std::chrono::milliseconds dura(5000); //模拟一些耗时的过程
      	std::this_thread::sleep_for(dura);
      	temp.set_value(calc);//结果保存到了promise的temp对象中
      }
      int main() {
      	std::promise<int> prs;  //声明一个std::promise对象,保存的值的类型是int
      	std::thread t1(thrd, std::ref(prs), 180);//似乎一定要使用std::ref
      	t1.join();
      	std::future<int> res = prs.get_future(); //promise和future绑定
      	std::cout << "get result " << res.get() << std::endl;
      }
      //由于线程间的按引用传值实质上还是值传递,不能直接传递引用来达到相同的效果
      
      future的其他参数
      1. std::future_status :枚举类型(由future类的wait_for和wait_until成员函数返回)

        std::future<int> res = std::async(myThread,42); //启动线程,绑定future类
        std::future_status status = res.wait_for(std::chrono::seconds(1));//等待此线程一秒钟
        if (status == std::future_status::timeout) {
        	/*表示线程超时*/
        } else if (status == std::future_status::ready) {
        	/*表示线程执行完毕*/
        } else {
            std::cout << "thread deferred" << std::endl;
        }
        
        //future的成员函数future_status wait_for(const chrono::duration<_Rep, _Per>& _Rel_time) 表示等待线程一定时间,返回future_status枚举类型:std::future_status::ready,std::future_status::timeout,std::future_status::deferred(当async指定了std::launch::deferred参数)
        
      2. future的get()成员函数使用的是移动语义,其结果只能被获取一次,若希望多次(或多个线程都要获取)结果,可以使用std::shared_future类模板

        //转换future
        std::future<int> result = std::async(myThread,42);
        std::shared_future<int> res(result.shared()); //或... res(std::move(result));
        //future 的成员函数valid可以判断future里是否含有有效值(即有没有被获取)
        if (result.valid()) result.get();
        //也可以直接使用shared_future
        std::shared_future<int> res = mypt.get_future();// = std::async(myThread,42);
        
      原子操作
      1. 可以把原子操作理解为不需要互斥量的多线程并发编程方式,实在多线程中不会被打断的程序执行片段,原子操作比互斥量更节省资源(互斥量针对对行代码,原子操作针对一个变量)
      2. 原子操作一般是不可分割的操作,要么是完成的,要么是并未开始的
      3. 一般atomic原子操作,针对++,–,+=,&=,|=,~=是支持的。其他的可能不支持
      std::atomic<int> count = 0;
      
      void test() {
      	for (int i = 0; i < 100000; ++i) 
              ++count; //若改为count = count+1,则结果不正确
      }
      
      int main() {
      	std::thread t1(test);
          std::thread t2(test);
          t1.join();
          t2.join();
          std::cout << count <<std::endl;
      }
      //output 200000
      
      std::async()深入
      1. 第一个可选的参数std::launch::deferred(延迟到返回的future类调用get函数或者wait函数时调用入口函数,并不创建新线程)和std::launch::async(强制异步任务在新线程中执行);若指定参数 std::launch::async | std::launch::deferred ,意味着由系统选择两种方式,若不指定第一个参数,直接传入入口函数及参数,则以此为默认值
      2. std::thread()如果系统资源紧张,那么可能创建线程失败,程序可能因此崩溃,而std::async是创建一个异步任务,可以根据系统资源选择异步进行或者同步进行
      3. std::thread创建线程的方式,想获取其返回值并不容易。async返回一个future类以获得执行结果
    补充
    1. 虚假唤醒

      • condition_variable在wait的过程中,有线程可能多次调用notify_one或notify_all唤醒本线程,然而数据被(其他线程)处理而使得没有数据可供处理,使得本线程被虚假唤醒
      • 为防止虚假唤醒,wait()中要有第二个参数,并且该参数应当正确判断数据是否存在
    2. 原子操作

      std::atomic<int> count = 0;
      std::cout << count << std::endl; //对count的读操作是原子操作然而整个语句不是原子操作
      auto atomic2(count); //error  原子模板类的赋值/拷贝操作被删除
      auto atm3(count.load()); //但可以使用原子操作进行复制
      atm3.store(12); //原子操作,赋值
      
    3. 线程池(待续)

      • C/S模型:创建线程时的消耗太大,先行创建一个线程池,有任务时分配给空闲的线程,完成后线程挂起
      • 线程的数量极限:2000个线程是极限
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值