当前位置: 首页 > news >正文

C++-第十八章:线程相关内容

目录

第一节:thread的主要内容

        1-1.创建子线程

        1-2.回收子线程

        1-3.获得子线程的id

        1-4.获得当前线程id

        1-5.子线程传引用

        1-6.线程的先创建后使用

第二节:mutex的主要内容

        2-1.mutex的作用

        2-2.智能锁

第三节:condition_variable的主要内容

        3-1.休眠线程

         3-2.唤醒线程

下期预告:


第一节:thread的主要内容

        C++11引入了<thread>库来管理线程,它将线程包装成一种类来管理。

        1-1.创建子线程

std::thread t1(可调用对象,可变参数);

        t1:这个线程的管理句柄,主线程通过它管理这个子线程。

        可调用对象:这个线程创建时就会执行的函数,又叫任务

        可变参数:如果任务有参数,就在这里传入。

        1-2.回收子线程

        

t1.join();

        这行代码一般由主线程调用,而且它是阻塞的,即主线程等待子线程 t1 的任务完成之后再退出,如果不等待而主线程先退出,主线程的数据被回收。由于同一进程的线程之间的很多数据都是共享的,就会影响子线程的功能。

        

        1-3.获得子线程的id

t1.get_id();

         系统给每个线程都赋予了一个唯一的id,用来管理所有的线程,主线程可以使用上述代码获取某个子线程的id。

        1-4.获得当前线程id

this_thread::get_id();

        线程可以使用上述代码获取自己的id,主线程也可以获取自己的id。 

        1-5.子线程传引用

        子线程执行的任务函数也可以传引用,除了形参的位置用引用接受外,传参数时必须用ref()括起来:

void task(int& a)
{
	//...
}
int main()
{
	int a = 1;
	std::thread t1(task,ref(a));

	// 等待线程退出
	t1.join();
	return 0;
}

        1-6.线程的先创建后使用

        线程在被创建时,如果不传入任何任务函数时是被阻塞的:

	std::vector<std::thread> thrpool(10); // 创建10个线程,但不执行函数

        线程不支持拷贝构造,但是支持移动构造和移动赋值,那么就可以使用具有右值属性的std::thread类进行赋值,让thrpool中的线程运行起来:

void task()
{
	std::cout << "线程执行任务" << ",id:"<<std::this_thread::get_id() << std::endl;
}
int main()
{
	std::vector<std::thread> thrpool; // 创建10个线程,但不执行函数
	thrpool.resize(10);
	// 移动赋值
	for (auto& thread : thrpool)
	{
		thread = std::thread(&task);
		Sleep(2);
	}

	// 等待所有线程结束
	for (auto& thread : thrpool)
	{
		thread.join();
	}
	return 0;
}

   

第二节:mutex的主要内容

        2-1.mutex的作用

        mutex意为锁,它用来锁住某些共享资源,防止引发线程安全的问题,请看以下的例子:

#include <thread>
#include <windows.h>

int tickets = 1000; // 票数

void buyTicket()
{
    while (true)
    {
        if (tickets > 0)
        {
            tickets--;
            std::cout << "线程: " << std::this_thread::get_id()
                << " 购买了一张票, 剩余票数: " << tickets << std::endl;
        }
        else
        {
            break; // 如果没有票了,退出循环
        }
    }
}

int main()
{
	std::thread t1(buyTicket);
	std::thread t2(buyTicket);
	std::thread t3(buyTicket);

	// 等待线程退出
	t1.join();
	t2.join();
	t3.join();
	return 0;
}

         我让3个线程抢票,当票为0时退出,按理来说每个线程抢到票后,剩余的票数应该是不同的,但是上述代码不够完善,可能会出现剩余票数为负数的情况。

         因为CPU是以时间片轮转的形式运行线程,如果线程1进入 if 后,此时tickets为1,线程1还未执行 tickets-- 就被剥离CPU了,线程2判断 if 时,因为tickets还是1,线程2也会执行一次 tickets-- 。        

        然后线程1回来之后也会执行一次 tickets-- 。这就导致为1的tickets被执行了两次--,它的值就变成-1了。

        为了避免这种情况,需要保证进入 if 的线程同时只有一个,这就需要用到mutex。

        mutex是一种资源,同时只有一个线程能拥有它,其他线程就会在mutex的位置进行阻塞等待,直到拥有它的线程把mutex释放掉:

std::mutex mtx; // 初始化一个锁
void buyTicket()
{
    while (true)
    {
        mtx.lock(); // 上锁:线程获取锁
        // 检查和修改 tickets 没有同步
        if (tickets > 0)
        {
            --tickets;
            std::cout << "线程: " << std::this_thread::get_id()
                << " 购买了一张票, 剩余票数: " << tickets << std::endl;
        }
        else
        {
            mtx.unlock(); // 解锁:线程释放锁
            break; // 如果没有票了,退出循环
        }
        mtx.unlock(); // 解锁:线程释放锁
    }
}

  

        这样就正常了。

        mutex的意思就是锁,它就像锁一样,锁住其他线程,不让它们继续执行代码,直到拥有锁的线程解锁。 

        2-2.智能锁

        就像new空间的指针一样,如果出作用域后没有释放锁,那么其他线程就会一直等待锁,线程就不能正常退出了,所以C++引入了智能锁。

        智能锁需要一个锁进行构造,构造成功后会自动上锁,出作用域它会析构,自动解锁

std::mutex mtx;
void buyTicket()
{
    while (true)
    {
        std::unique_lock<std::mutex> lock(mtx); // 智能锁
        if (tickets > 0)
        {
            --tickets;
            std::cout << "线程: " << std::this_thread::get_id()
                << " 购买了一张票, 剩余票数: " << tickets << std::endl;
        }
        else
        {
            // 出作用域自动解锁
            break;
        }
        lock.unlock(); // 未出作用域,手动解锁
    }
}

 

第三节:condition_variable的主要内容

        condition_variable提供了条件变量相关接口,它需要配合锁使用。

        3-1.休眠线程

std::mutex mtx;
std::condition_variable con;

std::unique_lock<std::mutex> lock(mtx)
con.wait(lock);

        线程执行 con.wait(mtx) 时就会一直被休眠阻塞。

        条件变量阻塞线程的原理是让持有对应锁的线程释放锁,并使之休眠,等待唤醒。

        注意条件变量只允许传入智能锁(unique_lock),而不允许直接传入锁(mutex)。

         3-2.唤醒线程

con.notify_one(); // 随机唤醒一个线程
con.notify_all(); // 唤醒所有线程

        唤醒一个线程时,该线程直接就可以获得条件变量中的锁来执行后面的代码了,其他线程继续休眠。

        唤醒所有线程后,这些线程仍然需要先竞争条件变量中的锁,竞争到锁的一个线程才能执行后面的代码,其他线程没有继续休眠,而是阻塞等待锁被释放,然后竞争锁。

        wait的第二个参数还可以传入一个可调用对象,线程被唤醒时,还需要可调用对象的返回值为真时才能获得锁。

        不传入默认为真。

std::mutex mtx;
std::condition_variable con;
bool ready = false;
void worker(int id)
{
    std::unique_lock<std::mutex> lock(mtx);
    con.wait(lock, [] {return ready; });

    // 唤醒后仍需持有锁才能执行下面的代码
    std::cout << "线程 " << id << " 被唤醒并执行。" << std::endl;
}

int main()
{
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);
    std::thread t3(worker, 3);

    // 确保所有线程都进入等待状态
    std::this_thread::sleep_for(std::chrono::milliseconds(100));

    // 唤醒所有线程
    std::cout << "唤醒所有线程" << std::endl;
    ready = true; // 设置为真
    con.notify_all();

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

    return 0;
}

  

 

下期预告:

        第十九章将讲述C++11引入的另一种概念——异常。

        它可以帮助程序员更快的定位错误。

相关文章:

  • 如何用 TikTok 的创作工具提升你的视频质量?
  • Magic 1-For-1: 在一分钟内生成一分钟视频片段(基于Python实现,视频生成模型)
  • SpringBoot 多环境配置
  • leetcode141.环形链表,142环形链表ii
  • C++理解(六)
  • Unity3D 层级管理与标签管理详解
  • 【自用】NLP算法面经(4)
  • 【智能机器人开发全流程:硬件选型、软件架构与ROS实战,打造高效机器人系统】
  • 【虚拟机 IP 配置深度剖析】
  • Typescript 5.8 发布
  • [Qt5] QJson数据之间的转换以及QByteArray图像数据压缩
  • ACM-BufferedWriter---格式化输出浮点数
  • ADC采集模块与MCU内置ADC性能对比
  • GitHub开源协议选择指南:如何为你的项目找到最佳“许可证”?
  • 记一次按键中断的bug
  • C++中函数的调用
  • 【论文笔记】Attentive Eraser
  • 利用Java爬虫获取1688店铺所有商品信息:实战指南
  • 探秘基带算法:从原理到5G时代的通信变革【七】FFT/DFT
  • C++11之右值引用
  • 企业网站建设/西安百度推广竞价托管
  • 网络编程就是做网站么/武汉楼市最新消息
  • 长春房产网签查询/网络营销优化
  • 网站论坛建设方案/苏州网络推广seo服务
  • 静态网站建设平台/网站安全检测工具
  • 网站备案简介怎么写/广告推广营销网站