【c++】c++线程库的基本使用

本文详细介绍了C++线程库中的thread、mutex、lock_guard、unique_lock、condition_variable和C++20的counting_semaphore,涵盖了线程管理、基础同步机制、高级同步以及并发控制的各个方面。
摘要由CSDN通过智能技术生成
在这里插入图片描述


📄前言

在C++线程库推出之前,如果要实现跨平台多线程,那么我们就得需要直到每个平台的线程API的知识,这无疑对每个程序员都是不小的挑战,毕竟大部分人都不愿意去一一学习这些接口,而线程库的推出,封装了线程底层的实现,不仅提高了代码的可移植性,还减少了C++的学习压力。

C++线程库

创建线程

thread是c++11引进的线程管理机制,主要用于封装系统底层的线程API, 例如windows的 CreateThread 和linux的pthread_create,从而提高代码的可移植性。

  • thread的函数原型与参数
template <class Function, class... Args>
explicit thread (Function&& f, Args&&... args);
// 创建一个线程,可用lamda、函数指针+参数等形式

id get_id() const noexcept;	

bool joinable() const noexcept;
  • 成员函数
    • get_id:返回线程id
    • joinable:检查一个线程是否可以被回收。
    • join:回收线程的资源,防止僵死线程,如果线程没有执行完,则阻塞等待。
    • detach:与主线程分离,线程执行完毕后自动回收资源。

函数使用示例:

void func(std::string str)
{
	std::cout << std::this_thread::get_id() << std::endl;
    for(int i = 0; i < 10; i++)
    {
        std::cout << str << ":" << i << std::endl;
    }
}

int main() {
    std::thread t1(func, "thread 1");
    std::thread t2([]() { func("thread 2"); });

    t1.join();
    t2.join();

    return 0;
}

互斥量

mutex

mutex 是c++线程库最基础的互斥机制,用于保护临界资源不被多个线程同时访问,mutex 只提供了基础的锁和解锁接口。

class mutex {
public:
	lock();
	unlock();
	try_lock();	
}
  • 成员函数:
    • lock:锁定互斥量,只允许一个线程访问,
    • unlock:解锁互斥量,
    • try_lock:尝试锁定互斥量,如果互斥量被锁定则不阻塞,一直检查锁是否解锁(返回0),直到锁被解锁(返回1)。

lock_guard

lock_guard 与互斥量一起使用,提供一种RAII风格的管理机制(析构时自动调用unlock),避免忘记解锁而导致死锁的情况。

template <class Mutex> class lock_guard;

unique_lock

unique_lock 的基础用法与lock_guard一样,但比它提供了更多的功能,如控制锁的时间、方式,对条件变量的支持,以及可被移动(不可被复制)

template <class Mutex> class unique_lock;

template <class Clock, class Duration>
  bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);

template <class Rep, class Period>
  bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);
  
// 复制构造
unique_lock (const unique_lock&) = delete;
// 移动构造
unique_lock (unique_lock&& x);
  • 常用成员函数:
    • try_lock:不阻塞地申请锁
    • try_lock_for:与chrono类一起使用,等待指定的时间后去申请锁,申请失败继续等待。
    • try_lock_until:在指定的时间到底前都能一直申请锁,时间到达但没获得锁,则继续执行其他任务。

函数使用示例:

void fun_c(std::string name, int n)
{
    std::unique_lock<std::timed_mutex> lock(t_mtx, std::defer_lock);
    if(lock.try_lock_for(std::chrono::seconds(n)))
    {
        std::cout << name << " ";
        for(int i = 0; i < 10; ++i)
        {
            std::unique_lock<std::mutex> lock(mtx);
            std::cout << counter++ << std::endl;
        }
        std::this_thread::sleep_for(std::chrono::seconds(6));
    }
    else 
        std::cout << "can't lock" << std::endl;

    std::cout << name << "end" << std::endl;
}

int main() {
    std::thread t1(fun_c, "thread 1:", 2);
    std::thread t2(fun_c, "thread 2:", 2);

    t1.join();
    t2.join();
    return 0;
}

同步机制

condition_variable

condition_variable 是对系统条件变量的包装,用于实现线程直接的等待通知机制,需要与互斥量一起使用,拥有两大功能 wait 和 notify。

class condition_variable;
  • 成员函数:
    • wait:线程在这个函数中等待,直到接受到信号通知,和unique_lock 一样,它也有wait_for 和 wait_until,需要与互斥量一起使用。线程等待时会将锁给解锁。
    • notify_once:通知一个等待着的线程,并唤醒它。
    • notify_all:通知所有在wait等待着的线程,唤醒它们。

函数使用

std::condition_variable cv;
std::mutex mtx;
bool flag = false;
int counter = 0;

void producer(std::string name)
{
    // 通过条件变量可以控制线程的执行顺序
    std::cout << name << std::endl;
    for(int i = 0; i < 10; ++i)
    {
        {
            std::unique_lock<std::mutex> lock(mtx);
            while(flag)	
                cv.wait(lock);	// wait时会自动将锁解锁
            std::cout << ++counter << std::endl;
            flag = !flag;
            cv.notify_one();
        }
		std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

void consumer(std::string name)
{
    std::cout << name << std::endl;

    for(int i = 0; i < 10; ++i)
    {
        {
            std::unique_lock<std::mutex> lock(mtx);
            while(!flag)
                cv.wait(lock);

            std::cout << ++counter << std::endl;
            flag = !flag;
            cv.notify_one();	//唤醒一个线程
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}


int main() {
    std::thread t1(producer, "thread 1");
    std::thread t2(consumer, "thread 2");

    t1.join();
    t2.join();
    return 0;
}

C++20 信号量 的工作原理。

在c++20中新增了轻量级的线程控制机制——信号量,我们可以为信号量设定最大值与初始值,当信号量达到0或者最大值时,线程会阻塞,直到其不为0或最大值。

// 头文件 <semaphore>
template< std::ptrdiff_t LeastMaxValue = /* implementation-defined */ >
class counting_semaphore;
//尚未定义ptrdiff_t 一般为int,用于定义信号量的最大值。
  • 成员函数:
    • release:释放信号量,将信号量计数加 1 ,如果计数达到最大值则阻塞等待,知道信号量被消费。
    • acquire:申请信号量,将信号量的计数减 1 ,当计数为零或为满则阻塞等待,直到其他线程释放信号量。
    • try_acquire:尝试去减少信号量的计数,如果信号量为零则返回false,并且一直申请信号量直至成功。一般用于信号量消费较快的场景。

函数使用

int counter = 0;

void producer(std::string name)
{
    
    std::cout << name << std::endl;
    for(int i = 0; i < 10; ++i)
    {
        sem.try_acquire();	//信号量也可以用来代替锁
        std::cout << ++counter << std::endl;
        sem.release();
    }
}

void consumer(std::string name)		
{
    std::cout << name << std::endl;

    for(int i = 0; i < 10; ++i)
    {
        sem.try_acquire();
        std::cout << ++counter << std::endl;
        sem.release();
    }
}


int main() {
    std::thread t1(producer, "thread 1");
    std::thread t2(consumer, "thread 2");

    t1.join();
    t2.join();
    return 0;
}

📓总结

功能类别关键特性描述
线程管理std::thread抽象系统级线程,简化线程创建和管理。
基础同步mutex, lock_guard, unique_lock提供基本的线程同步机制,如互斥锁,以及自动锁管理工具。
高级同步condition_variable使线程能在特定条件下挂起和唤醒,优化资源利用和线程协调。
C++20新特性counting_semaphore引入信号量概念,为线程提供更细粒度的控制,如限流并发操作。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值