算法移植优化(四)c++11 多线程

c++11多线程库:std::thread

一、join函数:用于等待线程对象运行结束

程序从main函数开始,本来由一个线程执行;当执行到std::thread定义一个线程对象,给定初始构造函数后,就创建了一个新的子线程,并进行子线程独立计算;此时线程数量一分为二。

#include <iostream>
#include <thread>
#include <random>
void test_thread(){
    std::cout<<"hello world"<<std::endl;
}
int main() {//初始main函数,主线程
    std::thread t(test_thread);//创建了另一个线程

    t.join();//主线程等待子线程结束,才能执行下一条语句

    return 0;
}

二、detach函数:子线程从主线程中分离

这种情况下,就相当于子线程已经自力更生,可能主线程已经挂了,子线程也可以继续生存,这种就类似于后台运行线程。

#include <iostream>
#include <thread>
#include <math.h>
void test_thread(){
    for (int i = 0; i < 1000; ++i) {
       float si=sqrt(i);
        std::cout<<"thread:"<<i<<"\t"<<si<<std::endl;

    }

}
int main() {//初始main函数,主线程
    std::thread t(test_thread);//创建了另一个线程
    t.detach();//分离子线程,子线程于主线程不相关,无需等待,这种情况下,整个main函数结束运行,可能子线程还在运行
    for (int i = 0; i < 100; ++i) {
        float si=sqrt(i);
        std::cout<<"main:"<<i<<"\t"<<si<<std::endl;
    }

    return 0;
}

这个程序可以看到,两个线程交互打印出相关信息,但是一旦主线程main循环到了100次,程序运行结束了,子线程还在运行,不过控制台main主线程已经不能输出。

三、多个子线程

#include <iostream>
#include <thread>
#include <random>
void test_thread(int i){
    float sum=1;
    for(int j=0;j<1000;j++){
        sum+=j*0.978;
    }
    std::chrono::milliseconds dura(i*100);
    std::this_thread::sleep_for(dura);
    std::cout<<"thread:"<<i<<"output:"<<sum<<"\n"<<std::endl;
}
int main() {
    std::thread threads[5];//创建5个线程对象
    for (int i = 0; i < 5; i++) {
        threads[i] = std::thread(test_thread, i);//启动各个线程,并计算
    }
    for (auto& t: threads) {
        t.join();//等待所有的线程结束
    }
    std::cout << "end all thread"<<std::endl;
    return 0;
}

四、线程对象的移动、线程ID

#include <iostream>
#include <thread>
#include <math.h>
#include <sstream>

void test_thread(){
    auto myid = std::this_thread::get_id();//获取线程ID,
    std::stringstream ss;
    ss << myid;
    std::cout<<ss.str()<<std::endl;

}
int main() {//初始main函数,主线程
    test_thread();//线程1
    std::thread t1(test_thread);//创建了另一个线程
    std::thread t2(std::move(t1));//线程移动,类似于线程剪切,可以看到打印出来的是线程2
    t2.join();


    return 0;
}
线程对象被剪切后,此时t1将不代表任何线程,后续不要继续调用t1.join()

五、互斥量

以前在用openmp的时候,经常出现的名词:互斥锁。互斥量说白了,就是为了访问共享变量读取,需要开启的锁,以防止多个线程同时对某个共享变量进行修改操作,而引起混乱。这个在编写线程池的时候,尤其重要,任务队列就是共享变量,不允许多个线程共同进行入队、出队操作。

看一下下面这个例子,如果对共享变量count的累加,不使用互斥锁的话,会引起错乱,最后count的值不确定,应该是位于10w到20w之间的一个数:

#include <iostream>
#include <thread>
#include <math.h>
#include <sstream>
#include <mutex>

std::mutex lock;
int count=0;
void test_thread(){
   // lock.lock();
    for(int i=0;i<100000;i++){
        count++;
    }
   // lock.unlock();
}
int main() {//初始main函数,主线程
    std::thread t1(test_thread);//创建了另一个线程
    std::thread t2(test_thread);//线程移动,类似于线程剪切,可以看到打印出来的是线程2
    t1.join();
    t2.join();

    std::cout<<count<<std::endl;
    return 0;
}
主要原因就是两个线程同时访问、修改共享变量,而引起的。因此需要采用互斥量,在某个线程访问修改的时候,另外一个线程不允许修改访问,被阻塞在lock()语句,直到获得互斥量所有权为止。如果有其它线程在访问共享变量,那么你就需要等一等。

#include <iostream>
#include <thread>
#include <math.h>
#include <sstream>
#include <mutex>

std::mutex lock;
int count=0;
void test_thread(){
    lock.lock();
    for(int i=0;i<100000;i++){
        count++;
    }
    lock.unlock();
}
int main() {//初始main函数,主线程
    std::thread t1(test_thread);//创建了另一个线程
    std::thread t2(test_thread);//线程移动,类似于线程剪切,可以看到打印出来的是线程2
    t1.join();
    t2.join();

    std::cout<<count<<std::endl;
    return 0;
}
六、条件变量condition_variable

条件变量是另一种同于等待通知的同步机制,当某个线程运行到语句:condition_variable变量调用成员函数wait语句的时候:如果wait函数的第二个参数是true,线程继续运行;如果wait函数的第二个参数为false,线程将在该语句进行等待,等到condition_variable对象调用notify_one、notify_all的时候,此时会通知wait重新进行判断第二个参数的真假情况,判断时候继续等待。

condition_variable变量,一般配合std::unique_lock<std::mutex>进行等待:

#include <iostream>
#include <thread>
#include <math.h>
#include <sstream>
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<std::string>test_queue;

void test_thread(){
    std::unique_lock<std::mutex>ulock(mtx);
    cv.wait(ulock,[]{ return test_queue.size()>=10;});//判断队列是否满足某个条件,如果满足了,那么继续往下执行
    test_queue.push("t1 thread");

}
int main() {//初始main函数,主线程
    std::thread t1(test_thread);//创建了另一个线程
    for(int i=1;i<15;i++){
        cv.notify_all();//唤醒所有线程,此时会去t1线程中,判断条件变量cv.wait阻塞条件,如果为true,那么就t1就无需继续等待,往下执行
        std::chrono::milliseconds time(1000);
        std::this_thread::sleep_for(time);
        test_queue.push("main thread");
    }


    while (!test_queue.empty()){
        std::cout<<test_queue.front()<<std::endl;
        test_queue.pop();
    }
    t1.join();
    return 0;
}
上面例子,在编写线程池中,尤为重要,因为我们可以用于判断任务队列是否满了、是否为空,然后线程才能执行。

七、原子变量

上面在遇到共享变量的时候,需要使用mutex加锁,防止多个线程同时修改共享变量,挺麻烦的。c++11多线程还有个牛逼的类型,称之为原子类型:std::atomic,这种类型的共享变量,可以自动实现互斥功能,不需要互斥量来声明保护它。






fdaf

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值