C++多线程:thread

进程与线程

进程:系统资源分配的最小单元,通常被定义为一个正在运行的程序实例
线程:系统任务调度的最小单元

进程间通信:管道,信号量,信号,消息队列,共享内存,套接字
线程间通信:锁机制,信号量机制,信号机制,屏障

同步:保证任务片段的先后顺序
互斥:为了保证资源在同一时刻只能被一个线程使用,即数据一致性

同步方式

  • 原子操作
  • 信号量(semaphore)
  • 读写信号量(rw_semaphore)
  • 互斥锁
  • 自旋锁(spinlock)
  • 读写锁(rwlock)
  • 顺序锁(seqlock)

有序资源分配法
银行家算法,死锁
生产者消费者问题
读者写者问题
哲学家就餐问题,互斥

C++多线程编程涉及的头文件如下

<thread>
<mutex>
<condition_variable>

<future>
<promise>
<packaged_task>

<atomic>

<async>

死锁

产生死锁的四个必要条件

条件 说明
互斥 资源同一时刻只能被一个进程使用
请求并保持 进程在请资源时,不释放自己已经占有的资源
不剥夺 进程已经获得的资源,在进程使用完前,不能强制剥夺
循环等待 进程间形成环状的资源循环等待关系

死锁预防

破坏死锁产生的四个条件

  • 打破互斥条件
  • 打破不剥夺条件
  • 打破请求并保持条件
  • 打破循环等待条件

死锁避免

对分配资源做安全性检查,确保不会产生循环等待,如银行家算法

死锁检测

允许死锁的发生,但提供检测方法

死锁解除

已经产生死锁,强制剥夺资源或撤销线程

C++11线程库

C++11中定义了标准的线程库<thread>,位于std命名空间,编译时添加参数-std=c++11

构造函数

  • 默认构造函数
    thread()noexcept;
    创建一个空的thread对象,该对象是不可joinable的
  • 初始化构造函数
    template<typename Fn, typename... Args>
    explicit thread(Fn&& fn, Args&&... args);
    
    创建一个可joinablethread对象
    新产生的线程会调用fn(),实参由args给出

拷贝构造函数

thread(const thread&)=delete;

被禁用,thread对象不可拷贝

移动构造函数

thread(thread&& x) noexcept;

将一个临时(匿名的)thread对象赋值给另一个thread对象,调用成功后,x将不代表任何thread对象

注意,每个thread对象在销毁(析构)前,要么调用join()让主线程等待子线程执行完成
要么调用detach()将子线程和主线程分离,两者必选其一,否则程序可能存在以下两个问题:

  • 线程占用的资源将无法全部释放,造成内存泄漏
  • 当主线程执行完成而子线程未执行完时,程序执行将引发异常

示例如下:

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
 
void f1(int n) {
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread " << n << " executing\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
void f2(int& n) {
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
int main() {
    int n = 0;
    std::thread t1; // t1 is not a thread
    std::thread t2(f1, n+1); // pass by value
    std::thread t3(f2, std::ref(n)); // pass by reference
    std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
    t2.join();
    t4.join();
    std::cout << "Final value of n is " << n << '\n';
}

赋值操作

  • 拷贝赋值
    thread& operator=(const thread&)=delete;
    
    被禁用,thread对象不可拷贝
  • 移动赋值
    thread& operator=(thread&& rhs)noexcept;
    
    若被赋值对象是非joinable的,则需要传递一个右值引用rhs,否则terminate()报错

示例如下:

#include <stdio.h>
#include <stdlib.h>

#include <chrono>    // std::chrono::seconds
#include <iostream>  // std::cout
#include <thread>    // std::thread, std::this_thread::sleep_for

void thread_task(int n) {
    std::this_thread::sleep_for(std::chrono::seconds(n));
    std::cout << "hello thread "
              << std::this_thread::get_id()
              << " paused " << n << " seconds." << std::endl;
}

int main(int argc, char *argv[]) {
    std::thread threads[5];
    std::cout << "Spawning 5 threads...\n";
    for (int i = 0; i < 5; i++) {
        threads[i] = std::thread(thread_task, i+1);
    }
    std::cout << "Done spawning threads! Now wait for them to join\n";
    for (auto& t: threads) {
        t.join();
    }
    std::cout << "All threads joined.\n";

    return EXIT_SUCCESS;
}

其他成员函数

函数 说明
joinable() 判断线程对象是否支持调用join函数
join() 阻塞线程对象所在的当前线程,直到线程对象执行完毕
detach() 将线程对象从该对象所在线程分离,使其彼此独立执行
swap() 交换两个线程对象所代表的底层句柄underlying handlers
native_handle() 返回与std::thread的具体实现相关的线程句柄
hardware_concurrency()[static] 检测硬件并发特性,返回当前平台的线程实现所支持的线程并发数目,但返回值仅仅只作为系统提示(hint)

注意

  1. 若某个线程已经执行完任务,但没有被join的话,该线程依然会被认为是一个活动的执行线程,因此也是可被join的
  2. 调用detach()
    • *this不再代表任何线程实例
    • joinable()==false
    • std::this_thread::get_id()==std::thread::id()

std::this_thread 命名空间中相关辅助函数

<thread>头文件中不仅定义了thread类,还提供了一个名为this_thread的命名空间,此空间中包含一些功能实用的函数

函数 说明
get_id() 获取当前线程对象id
yield() 阻塞当前线程,操作系统调度执行另一线程
sleep_until() 阻塞当前线程,直到某个时间点
sleep_for() 阻塞线程休眠指定的时间片time span,由于线程调度等原因,实际休眠时间可能比sleep_duration更长

示例如下:

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>

void f1(int n) {
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread " << n << " executing\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void f2(int& n) {
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

int main() {
    int n = 0;
    std::thread t1; // t1 is not a thread
    std::thread t2(f1, n+1); // pass by value
    std::thread t3(f2, std::ref(n)); // pass by reference
    std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
    // 阻塞主线程,等待t2执行完毕
    t2.join();
    // 阻塞主线程,等待t4执行完毕
    t4.join();
    std::cout << "Final value of n is " << n << '\n';
}
posted @ 2024-11-01 20:04  sgqmax  阅读(2)  评论(0编辑  收藏  举报