c++之多线程

三、 多线程

1. 创建线程

要想使用线程,需要导入头文件#include<thread>

#include <thread>

void show(){
    for(int i = 0 ; i < 10 ;i ++){
        cout <<__func__ << " = " << i  << endl;
        Sleep(1000);
    }
}


int main() {
    //让这个线程执行上面的show 函数
    thread t(show) ;
    cout << "执行了main函数 " <<endl;

    return 0;
}

2. join 和 detach

  • join 的意思是让主线程等待子线程执行结束后,在进行下一步,意思是让主线程挂起。 当两个线程公用同一个变量时,
int main() {
    //让这个线程执行上面的show 函数
    thread t(show) ;
    
    //让主线程等待子线程运行结束后,再继续下面的逻辑
    //否则主线程运行结束,程序就结束了。
    t.join();
    cout << "执行了main函数 " <<endl;

    return 0;
}
  • detach的意思将本线程从调用线程中分离出来,允许本线程独立执行,从此和主线程再也没有任何关系。(但是当主进程结束的时候,即便是detach()出去的子线程不管有没有完成都会被强制杀死) 。
int main() {
    //让这个线程执行上面的show 函数
    thread t(show) ;
    
    //让主线程等待子线程运行结束后,再继续下面的逻辑
    //否则主线程运行结束,程序就结束了。
    t.detach();
    cout << "执行了main函数 " <<endl;

    return 0;
}
  • 传递参数

往线程里面执行的函数传递参数,最长使用的办法就是bind机制 , 这里以在线程内部构建学生对象,从外部传递姓名和年纪数据。

class stu{
public:
    string name;
    int age;

    stu(const string &name, int age) : name(name), age(age) {
        cout <<"执行构造函数了~"  << name <<" = "<< age<< endl;
    }
}

void constructor(string name ,int age ){
    cout <<"执行构造学生的工作~·1" << endl;
    stu s(name ,age);
}

int main() {
    //这里会提示: 还不如使用lambda表达式呢~
  thread t(bind(constructor , "张三" , 16)) ;
}

3. 获取线程id 和 休眠

  • 获取线程id

每一个线程在执行的时候,都有自己的一个标识id, 只有在少数情况下,线程的id会变得与众不同。通过 t.get_id() 获取线程对一个的id , 也可以使用get_id() 获取当前线程的 id

void show(){
    cout <<"打印语句~" << endl;
}  
//这里会提示: 还不如使用lambda表达式呢~
thread t(show) ;

t.get_id();

//在函数内部获取当前线程的id 可以使用命名空间 this_thread里面的函数get_id
int main(){
    cout <<"主线程的id="<< this_thread::get_id() << endl;
}

  • 线程休眠

让线程休眠,等待一段时间然后继续执行,这样的场景在开发的时候经常会出现,在 c++中,让线程休眠,可以使用 windows.h头文件中的Sleep函数 也可以使用 this_thread:: 里面的 sleep_for 函数 .

void show(){
    for (int i = 0; i < 10; ++i) {
        cout <<"打印语句~" << endl;
       // Sleep(1000) ; //单位是毫秒;
        this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main(){
    thread t(show) ;
}
  • 结束线程

线程的退出,手段还是很多的,但是万般手段中,建议使用的只有一个。

1、自行手动退出(函数返回、条件标记 false 、抛出异常等等)(建议使用)
2、通过调用ExitThread函数,线程将自行撤消(最好不使用该方法)。
3、同一个进程或另一个进程中的线程调用TerminateThread函数(应避免使用该方法)。
4、ExitProcess和TerminateProcess函数也可以用来终止线程的运行(应避免使用该方法)。

void show(){
    for (int i = 0; i < 25; ++i) {
        if(i== 3){
            cout <<"函数返回,线程终止。" << endl;
            return ; //或者在这抛出异常,也形同return。
        }
    }
}
int main() {
  thread t(show) ;
}

4. 并发访问

由于cout对象并不会产生互斥,所以在多线程场景下,输出的结果并不是我们想要的,显得杂乱无章。这时候可以使用mutex 来控制互斥

mutex mutex1;

void fun(){
    int count=10;
    while(count>0){
        mutex1.lock(); //上锁, 从上锁到解开锁这段代码时互斥的。
        
        std::cout<<"thread_"<<this_thread::get_id()<<"...count = "<< count<<std::endl;
        count--;
        
        mutex1.unlock(); //释放锁
        
        Sleep(500);
      
    }
}

int main() {
    std::thread t1(fun);
    std::thread t2(fun);

    t1.join();
    t2.join();
    return 0;
}
  • 避免数据竞争

在多线程并发的场景下,,若在多个线程访问的同一块代码中含有可以共享的数据(对象、变量),那么就要格外小心了,因为有可能产生多个线程同时修改数据的情况,进而导致线程执行或者输出的结果与理想中的结果相差很多。 如果有大量的数据需要共享的场合,那么可以使用一些锁的机制来避免并发访问的问题:

互斥锁(mutex) : 也叫互斥量(互斥变量) 用于表示某个资源互斥访问权限的对象,如果要访问资源,那么必须先获取互斥锁,只有拿到锁才允许访问数据,访问结束后,记得释放锁,否则其他线程将无法获取到锁。

条件变量: 一个thread用条件变量等待另一个thread或计时器生成的事件 , 严格来说条件变量不能防止数据竞争,而是帮助我们避免引入可能引起可能引起数据竞争的共享数据

发布了40 篇原创文章 · 获赞 3 · 访问量 1385

猜你喜欢

转载自blog.csdn.net/hongge_smile/article/details/104248043
今日推荐