多线程的基本使用与多线程中条件变量的使用——消费者生产者问题实例

多线程的基本使用与多线程中条件变量的使用——消费者生产者问题实例

本文主要涉及多线程的使用方法,通过两个实例来对多线程的使用进行理解,
案例包括:
1.一个线程负责计数,另一个线程负责打印计数值
2.消费者生产者问题

文章目录

  • 多线程的基本使用与多线程中条件变量的使用——消费者生产者问题实例
    • 一、 多线程地基本用法
      • 1.1 多线程概念
      • 1.2 多线程的优势
      • 1.3 常用的多线程函数
      • 1.4 实际案例(一个负责计数一个负责打印)
      • 1.5 usleep在上面案例中地重要性
    • 二、 多线程的条件变量
      • 2.1 基本的条件变量操作:
      • 2.2 实际案例 - 生产者-消费者问题

一、 多线程地基本用法

1.1 多线程概念

线程线程是进程的一部分,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源(如内存),但每个线程都有自己的堆栈和局部变量。

多线程:多线程是指一个进程中有多个执行路径,每个执行路径都称为一个线程。多线程允许程序同时执行多个任务,增强了程序的并发性和响应性。

1.2 多线程的优势

  1. 响应性:当一个线程等待某些操作完成时,其他线程可以继续执行,提高了整体系统的响应性。
  2. 资源共享:多个线程可以共享同一个进程的资源,如内存,文件句柄等。
  3. 经济性:线程的创建和销毁比进程更为经济,因为线程间的上下文切换比进程间的上下文切换要快。

1.3 常用的多线程函数

  1. pthread_create:创建一个新的线程。
  2. pthread_join:等待一个线程结束。
  3. pthread_exit:线程自己退出。
  4. pthread_mutex_initpthread_mutex_lockpthread_mutex_unlock:用于创建和管理互斥锁,确保在某一时刻只有一个线程访问共享资源。
  5. pthread_cond_initpthread_cond_waitpthread_cond_signal:条件变量用于线程间的通信。

1.4 实际案例(一个负责计数一个负责打印)

考虑一个简单的场景:有两个线程,一个线程负责计数,另一个线程负责打印计数值。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>  // 用于 usleep 函数

int count = 0;
pthread_mutex_t count_mutex;

void *increment_counter(void *arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&count_mutex);
        count++;
        pthread_mutex_unlock(&count_mutex);
        usleep(300000);  // 休眠 300ms
    }
    pthread_exit(NULL);
}

void *print_counter(void *arg) {
    for (int i = 0; i < 10; i++) {
        pthread_mutex_lock(&count_mutex);
        printf("Count: %d\n", count);
        pthread_mutex_unlock(&count_mutex);
        usleep(300000);  // 休眠 300ms
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;

    pthread_mutex_init(&count_mutex, NULL);

    pthread_create(&thread1, NULL, increment_counter, NULL);
    pthread_create(&thread2, NULL, print_counter, NULL);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    pthread_mutex_destroy(&count_mutex);

    return 0;
}

在这里插入图片描述

在上述例子中,两个线程并发地增加和打印计数值,为了确保计数的正确性,使用了互斥锁。其中有两个线程:increment_counterprint_counter。其中,一个线程用于增加一个全局计数器 count 的值,而另一个线程则用于打印这个计数器的值。

以下是代码的工作流程:

  1. 全局变量和互斥锁:定义了一个全局变量 count 和一个互斥锁 count_mutex。互斥锁用于确保同时只有一个线程可以修改 count 的值,从而避免了竞争条件。

  2. 增加计数器的线程increment_counter 函数负责增加 count 的值。在每次增加之前,线程会锁定互斥锁,然后在增加后解锁它。此外,为了模拟一些处理时间,线程使用 usleep 函数休眠 300 毫秒。

  3. 打印计数器的线程print_counter 函数负责打印 count 的当前值。它也使用相同的互斥锁来确保在打印之前和之后正确地锁定和解锁。

  4. 主函数:在 main 函数中,首先初始化互斥锁。然后创建两个线程,一个用于增加计数器,另一个用于打印计数器的值。使用 pthread_join 确保主线程等待这两个线程完成后再继续执行。最后,清理互斥锁并退出程序。

运行此程序时,您应该会看到交替的输出,其中每个值都是先增加线程更改后的最新值。由于使用了互斥锁,所以不会出现不一致或竞争条件的情况。

1.5 usleep在上面案例中地重要性

如果没有这个延迟,可能会看到一些奇怪的行为,甚至可能出现全是10的情况。这是因为线程调度的机制。

在多线程环境中,操作系统会为每个线程分配时间片(也称为CPU时间),一个线程在分配的时间片用完后,操作系统可能会将其挂起并切换到另一个线程。这种切换是不确定的,即无法预测两个线程之间的交替顺序。

如果没有延迟,increment_counter 线程可能会迅速地递增 count 到10,然后print_counter线程开始运行,因为没有其他操作或延迟来改变这种行为。因此,可能会看到很多行都是 Count: 10

但是,当在关键部分(如对 count 的递增和打印)添加了延迟时,会更有机会看到这两个线程之间的交互,因为操作系统的线程调度会更频繁地进行线程切换,从而使得递增和打印的操作交错进行。

延迟在这里有助于模拟和增强多线程的交互效果,使得并发问题更容易观察和理解。

二、 多线程的条件变量

条件变量 (Condition Variables) 是一种线程同步机制,它允许一个或多个线程等待某个特定的条件得到满足后再继续执行。它通常与互斥量 (Mutex) 一起使用,以确保在检查条件和等待条件之间的操作是原子的。

2.1 基本的条件变量操作:

  1. 初始化条件变量:

    pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
    
  2. 等待条件满足:

    pthread_cond_wait(&cond_var, &mutex);
    

    此函数会释放互斥量并阻塞当前线程,直到另一个线程调用 pthread_cond_signalpthread_cond_broadcast 并重新获得互斥量。

  3. 发送信号:

    pthread_cond_signal(&cond_var);
    

    此函数唤醒一个等待在条件变量上的线程。如果有多个线程等待,那么哪一个会被唤醒是不确定的。

  4. 广播信号:

    pthread_cond_broadcast(&cond_var);
    

    此函数唤醒所有等待在条件变量上的线程。

2.2 实际案例 - 生产者-消费者问题

考虑一个经典的生产者-消费者问题,其中生产者线程在一个有限的缓冲区中放入数据,而消费者线程从缓冲区中取出数据。

#include <stdio.h>
#include <pthread.h>

#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
    for (int i = 0; i < 100; i++) {
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond_empty, &mutex);
        }
        buffer[count++] = i;
        printf("Produced: %d\n", i);
        pthread_cond_signal(&cond_full);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 100; i++) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {
            pthread_cond_wait(&cond_full, &mutex);
        }
        int value = buffer[--count];
        printf("Consumed: %d\n", value);
        pthread_cond_signal(&cond_empty);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main() {
    pthread_t prod_thread, cons_thread;
    
    pthread_create(&prod_thread, NULL, producer, NULL);
    pthread_create(&cons_thread, NULL, consumer, NULL);

    pthread_join(prod_thread, NULL);
    pthread_join(cons_thread, NULL);

    return 0;
}

在这个例子中,我们有一个缓冲区和一个互斥量来保护它。生产者和消费者线程都会检查缓冲区的状态,并在缓冲区满或空时等待条件变量的信号。当生产者放入数据时,它会唤醒消费者(如果它在等待)。反之亦然,当消费者消费数据时,它会唤醒生产者。这确保了生产者和消费者之间的同步和协调。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/267849.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Git常用命令及解释说明

目录 前言1 git config2 git init3 git status4 git add5 git commit6 git reflog7 git log8 git reset结语 前言 Git是一种分布式版本控制系统&#xff0c;广泛用于协作开发和管理项目代码。了解并熟练使用Git的常用命令对于有效地管理项目版本和历史记录至关重要。下面是一些…

[THUPC 2024 初赛] 二进制 (树状数组单点删除+单点查询)(双堆模拟set)

题解 题目本身不难想 首先注意到所有查询的序列长度都是小于logn级别的 我们可以枚举序列长度len&#xff0c;然后用类似滑动窗口的方法&#xff0c;一次性预处理出每种字串的所有出现位置&#xff0c;也就是开N个set去维护所有的位置。预处理会进行O(logn)轮&#xff0c;每…

基于谷歌模型gemini-pro 的开发的QT 对话项目

支持的功能&#xff0c;新建对话框&#xff0c;目前发现相关梯子不支持访问谷歌的api 的可能代理设置的不对&#xff0c; QNetworkAccessManager manager;// Set up your requestQNetworkRequest request;request.setUrl(QUrl("https://generativelanguage.googleapis.com…

这一平台只要把握住风口期,自己就能当老板!

我是电商珠珠 短视频渐渐走进大家的视野&#xff0c;改变了大家的日常娱乐方式。从19年开始&#xff0c;抖音开始发展电商平台-抖音小店。 在改变大家娱乐方式的同时&#xff0c;还将直播电商的热度掀了起来&#xff0c;由此改变了大家的购物方式&#xff0c;给大家带来了方便…

ansible-playbook实操之一键搭建lnmp+wordpress

目录 1、架构和准备&#xff1a; 2、配置nginx角色&#xff1a; 3、配置mariadb角色&#xff1a; 4、配置php角色&#xff1a; 5、配置完之后&#xff0c;写脚本调用roles 6、配置完之后浏览器搭建wordpress&#xff1a; 1、架构和准备&#xff1a; 操控节点&#xff1a;…

Echarts社区推荐

Apache Echarts官方示例中&#xff0c;有的demo并不能完全符合我们的需求&#xff0c;下面推荐几个Echarts社区&#xff0c;以便快速搭建项目。 1. isqqw 官方地址 &#xff1a;https://www.isqqw.com/ 2. makepie 官方地址 &#xff1a;https://www.makeapie.cn/echarts 3. P…

20231224解决outcommit_id.xml1 parser error Document is empty的问题

20231224解决outcommit_id.xml1 parser error Document is empty的问题 2023/12/24 18:13 在开发RK3399的Android10的时候&#xff0c;出现&#xff1a;rootrootrootroot-X99-Turbo:~/3TB/Rockchip_Android10.0_SDK_Release$ make installclean PLATFORM_VERSION_CODENAMEREL…

形态学处理

形态学处理的相关内容 &#xff08;1&#xff09;基于图像形态进行处理的一般方法 &#xff08;2&#xff09;这些处理方法基本是对二进制图像进行处理 &#xff08;3&#xff09;卷积核决定着图像处理后的结果 形态学图像处理 &#xff08;1&#xff09;腐蚀&#xff08;…

测试C#使用AForge从摄像头获取图片

百度“C# 摄像头”关键词&#xff0c;从搜索结果来看&#xff0c;使用OpenCV、AForge、window动态链接库获取摄像头数据的居多&#xff0c;本文学习基于Aforge.net连接摄像头并从摄像头获取图片的基本方法。   AForge相关包&#xff08;尤其是相关的控件&#xff09;主要针对…

【AIPRM】-高效管理Prompt模板,让你与众多AI互动更加流畅

关于AIPRM 链接: AIPERM AIPRM&#xff1a;Google 推出的AI提示管理工具。它提供多样化的Prompt模板&#xff0c;能帮助你与各种AI进行更加高效的互动。 登录 在主页点击“免费安装”–>Add to Chrome。 安装完成后&#xff0c;你在新的ChatGPT界面里面&#xff0c;能…

【四】记一次关于架构设计从0到1的讨论

记一次关于架构设计从0到1的讨论 简介&#xff1a; 在一次面试中和面试官讨论起来架构设计这个话题&#xff0c;一聊就不知不觉一个小时了&#xff0c;感觉意犹未尽。现在回想起来感觉挺有意思的&#xff0c;古人说独学而无友则孤陋而寡闻&#xff0c;的确是这样的&#xff0c…

基于SSM的搬家预约系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的搬家预约系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

css的定位

为什么需要定位&#xff1f; 场景&#xff1a; 某个元素可以自由的在一个盒子内移动位置&#xff0c;并且压住其他盒子当我们滚动窗口的时候&#xff0c;盒子是固定屏幕某个位置的。 这二个需求&#xff0c;使用标准流和浮动的方式是无法实现的或者是不容易实现&#xff0c;所以…

date-fns v3 发布——这个由 200 个函数组成的 JavaScript 日期处理套件

date-fns v3 发布——这个由 200 个函数组成的 JavaScript 日期处理套件已经在 TypeScript 中重写&#xff0c;重新引入了 String 日期参数&#xff0c;在 Node 上支持 ESM&#xff0c;并且所有函数现在都可以通过命名导出导出。 经过几个月的开发&#xff0c;v3 终于出来了&a…

手写Vue2源码

手写Vue2 使用rollup搭建开发环境 使用rollup打包第三方库会比webpack更轻量&#xff0c;速度更快 首先安装依赖 npm init -ynpm install rollup rollup-plugin-babel babel/core babel/preset-env --save-dev然后添加 rollup 的配置文件 rollup.config.js import babel f…

react 路由v6

这里是区别&#xff1a;V5 vs V6 这里是官网&#xff1a;可以查看更多高级属性 一、基本使用&#xff1a; 1、配置文件 src/routes/index import React from "react";const Home React.lazy(() > import("../Pages/Home")); const About React.laz…

探索 HTTP 请求的世界:get 和 post 的奥秘(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

html之如何设置音频和视频

文章目录 前言一、音频标签&#xff1a;audio1.audio简介2.常用属性controlsautoplayloop代码演示&#xff1a; 二、视频标签&#xff1a;video1.video2.常用的视频元素controlsautoplayloop代码演示&#xff1a; 总结视频元素总结音频元素总结 前言 html中插入音频和视频的方…

超维空间S2无人机使用说明书——51、使用yolov8进行目标跟踪

引言&#xff1a;为了提高yolo识别的质量&#xff0c;提高了yolo的版本&#xff0c;改用yolov8进行物体识别&#xff0c;同时系统兼容了低版本的yolo&#xff0c;包括基于C的yolov3和yolov4&#xff0c;以及yolov7。 简介&#xff0c;为了提高识别速度&#xff0c;系统采用了G…

14章总结

一.lambda表达式 1.lambda表达式简介 lambda表达式不能独立执行&#xff0c;因此必须实现函数式接口&#xff0c;并且会返回一个函数式接口的对象。 语法&#xff1a; ()->结果表达式 参数->结果表达式 (参数1&#xff0c;参数2&#xff0c;...&#xff0c;参数n)->…