[Linux高性能服务器编程] 多线程编程

news2025/2/24 16:26:31

本文初发于 “天目中云的小站”,同步转载于此。’

线程与进程

线程是轻量级的进程, 想要理解线程的关键, 首先要理解线程和进程之间的区别.

一个进程在创建之初其实就可以看作是一个主线程, 其创建出的线程其实和其本质无很大差别, 其实就多了一个线程共享资源罢了, 我们要明晰的是一个进程创建的不同线程之间, 什么是共享的, 什么是独立的.

线程间共享的 :

  • 进程地址空间中的代码段, 数据段, 堆
  • 文件描述符
  • 信号处理
  • 环境变量
  • 进程id

线程间独立的 :

  • 线程id
  • 进程地址空间中的栈
  • 寄存器状态
  • 信号掩码(会继承但独立)
  • 线程局部存储(TLS)
  • errno

只有理解了这些, 我们才可以分析出线程相比于进程, 我们还需要关心什么, 例如 :

  • 数据段 + 文件描述符共享 -> 需要锁来限制共享内存的访问
  • 信号处理共享 + 信号掩码独立 -> 可以创建一个线程单独管理信号.

线程创建及使用

#include<pthread.h>
typedef unsigned long int pthread_t;
int pthread_create(pthread_t*thread,const pthread_attr_t*attr,void*(*start_routine)(void*),void*arg);
void pthread_exit(void*retval);
int pthread_join(pthread_t thread,void**retval);

线程同步(线程间通信)

和线程同步相关的要点也有三种 :

POSIX信号量

进程间通信中已经将了System V信号量l, POSIX信号量的理念和使用方法也相似, 还是可以理解为一种可计数的高级互斥锁.

POSIX信号量的使用会更加方便一些 :

#include<semaphore.h>
int sem_init(sem_t*sem,int pshared,unsigned int value);
int sem_destroy(sem_t*sem);
int sem_wait(sem_t*sem);
int sem_trywait(sem_t*sem);
int sem_post(sem_t*sem);
  • sem : 就是标定唯一的信号量的一个标准, 直接创建即可sem_t sem;;
  • pshared : 用于指定信号量类型, 如果为0代表是进程局部的信号量, 不为0可以实现进程间共享, 不过这个信号量一般用于线程间通信, 所以用0即可.
  • value : 设置信号量的值, 和System V中semctl的作用一致.

互斥锁

这个就是常用的普通锁, 只能被一个线程持有.

#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t*mutex,const pthread_mutexattr_t*mutexattr);
int pthread_mutex_destroy(pthread_mutex_t*mutex);
int pthread_mutex_lock(pthread_mutex_t*mutex);
int pthread_mutex_trylock(pthread_mutex_t*mutex);
int pthread_mutex_unlock(pthread_mutex_t*mutex);

可以用宏的方式替代init :

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

init的第二个参数用于设置互斥锁属性, 一般是NULL.

条件变量

#include<pthread.h>
int pthread_cond_init(pthread_cond_t*cond,const pthread_condattr_t*cond_attr);
int pthread_cond_destroy(pthread_cond_t*cond);
int pthread_cond_broadcast(pthread_cond_t*cond);    
int pthread_cond_signal(pthread_cond_t*cond);
int pthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);

这个名称起的比较晦涩, 但是确实是和"条件"有关, 这个东西是和互斥锁绑定使用的, 你可以理解为互斥锁只负责确保当前只有一个线程可以持有锁, 也就是只用一个线程可以访问共享资源. 而条件变量是在确保这个共享资源属于可用状态, 可用状态的判断是靠条件实现的, 更具体的说就是通过if判断是否触发pthread_cond_wait函数.

以生产者消费者模型为例, 生产者将产物存入共享内存中, 消费者从共享内存中取出产物, 那么互斥锁可以保证同时只能由一个生产者或消费者访问共享内存,

  • 但是如果共享内存满了, 生产者再去生产就是浪费,
  • 如果内存为空, 消费者去取也是徒劳.

这时条件变量就可以发挥作用 :

  • 当共享内存满了, 可以将生产者挂起, 直到共享内存有产物被取走为止,
  • 当共享内存为空, 可以将消费者挂起, 直到共享内存不为空为止.
函数使用

函数使用和互斥锁相似, init的第二个参数还是设置为NULL就行, 并且也可以用下面的代码替代 :

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 我们通过条件判断触发pthread_cond_wait, 这个函数会将线程挂起, 并且释放当前持有的锁.
  • 在我们认为可以满足条件的地方触发pthread_cond_signal, 这个函数会选择一个挂起的线程并唤醒他, 被唤醒的线程会重新持有互斥锁并且继续执行pthread_cond_wait后面的代码.
CS模型代码案例
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
int shared_data = 0;

// 生产者线程函数
void* producer(void* arg) {
    pthread_mutex_lock(&mutex);  // 获取互斥锁
    while (shared_data != 0) {   // 条件判断
        pthread_cond_wait(&cond_var, &mutex);  // 等待条件变量
    }
    
    shared_data = 1;  // 生产数据
    printf("生产者生产数据: %d\n", shared_data);
    
    pthread_cond_signal(&cond_var);  // 唤醒消费者, 这时条件一定满足
    pthread_mutex_unlock(&mutex);  // 释放互斥锁
    return NULL;
}

// 消费者线程函数
void* consumer(void* arg) {
    pthread_mutex_lock(&mutex);  // 获取互斥锁
    while (shared_data == 0) {
        pthread_cond_wait(&cond_var, &mutex);  // 等待条件变量
    }
    
    shared_data = 0;  // 消费数据
    printf("消费者消费数据: %d\n", shared_data);
    
    pthread_cond_signal(&cond_var);  // 唤醒生产者, 这时条件一定满足
    pthread_mutex_unlock(&mutex);  // 释放互斥锁
    return NULL;
}

int main() {
    pthread_t producer_thread, consumer_thread;

    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);

    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_var);
    return 0;
}

线程同步机制包装类

#ifndef LOCKER_H
#define LOCKER_H
#include<exception>
#include<pthread.h>
#include<semaphore.h>

/*封装信号量的类*/
class sem
{
public:
    /*创建并初始化信号量*/
    sem()
    {
        if(sem_init(&m_sem,0,0)!=0)
        {
            /*构造函数没有返回值,可以通过抛出异常来报告错误*/
            throw std::exception();
        }
    }

    /*销毁信号量*/sem()
    {
        sem_destroy(&m_sem);
    }

    /*等待信号量*/
    bool wait()
    {
        return sem_wait(&m_sem)==0;
    }

    /*增加信号量*/
    bool post()
    {
        return sem_post(&m_sem)==0;
    }

private:
    sem_t m_sem;
};

/*封装互斥锁的类*/
class locker
{
public:
    /*创建并初始化互斥锁*/
    locker()
    {
        if(pthread_mutex_init(&m_mutex,NULL)!=0)
        {
            throw std::exception();
        }
    }

    /*销毁互斥锁*/locker()
    {
        pthread_mutex_destroy(&m_mutex);
    }

    /*获取互斥锁*/
    bool lock()
    {
        return pthread_mutex_lock(&m_mutex)==0;
    }

    /*释放互斥锁*/
    bool unlock()
    {
        return pthread_mutex_unlock(&m_mutex)==0;
    }

private:
    pthread_mutex_t m_mutex;
};

/*封装条件变量的类*/
class cond
{
public:
    /*创建并初始化条件变量*/
    cond()
    {
        if(pthread_mutex_init(&m_mutex,NULL)!=0)
        {
            throw std::exception();
        }
        if(pthread_cond_init(&m_cond,NULL)!=0)
        {
            /*构造函数中一旦出现问题,就应该立即释放已经成功分配了的资源*/
            pthread_mutex_destroy(&m_mutex);
            throw std::exception();
        }
    }

    /*销毁条件变量*/cond()
    {
        pthread_mutex_destroy(&m_mutex);
        pthread_cond_destroy(&m_cond);
    }

    /*等待条件变量*/
    bool wait()
    {
        int ret=0;
        pthread_mutex_lock(&m_mutex);
        ret=pthread_cond_wait(&m_cond,&m_mutex);
        pthread_mutex_unlock(&m_mutex);
        return ret==0;
    }

    /*唤醒等待条件变量的线程*/
    bool signal()
    {
        return pthread_cond_signal(&m_cond)==0;
    }

private:
    pthread_mutex_t m_mutex;
    pthread_cond_t m_cond;
};

#endif

不可重入函数

不可重入函数不能在多线程环境下使用, 因为其一般会使用到静态变量, 比如inet_ntoa等.

线程和信号

我们在上文已经知道线程之间信号处理共享并且信号掩码会继承但独立, 那么我们就可以设置一个单独的信号处理线程.

相关函数如下 :

#include<pthread.h>
#include<signal.h>
int pthread_sigmask(int how,const sigset_t*newmask,sigset_t*oldmask);
int sigwait(const sigset_t*set,int*sig);

构建流程如下 :

  • 在主线程构建出其他子线程之前就调用pthread_sigmask来设置好信号掩码,所有新创建的子线程都将自动继承这个信号掩码.
  • 在信号处理线程中调用sigwait来等待信号并处理之.
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>

#define handle_error_en(en,msg) do{errno=en;perror(msg);exit(EXIT_FAILURE);}while(0)

static void* sig_thread(void* arg)
{
    sigset_t* set = (sigset_t*)arg;
    int s, sig;
    for(;;)
    {
        /*第二个步骤,调用sigwait等待信号*/
        s = sigwait(set, &sig);
        if(s != 0)
            handle_error_en(s, "sigwait");
        printf("Signal handling thread got signal%d\n", sig);
    }
}

int main(int argc, char* argv[])
{
    pthread_t thread;
    sigset_t set;
    int s;
    /*第一个步骤,在主线程中设置信号掩码*/
    sigemptyset(&set);
    sigaddset(&set, SIGQUIT);
    sigaddset(&set, SIGUSR1);
    s = pthread_sigmask(SIG_BLOCK, &set, NULL);
    if(s != 0)
        handle_error_en(s, "pthread_sigmask");
    s = pthread_create(&thread, NULL, &sig_thread, (void*)&set);
    if(s != 0)
        handle_error_en(s, "pthread_create");
    pause();
}

线程局部存储(TLS)

线程局部存储(TLS)是一种允许线程拥有独立数据的技术。这意味着每个线程都可以访问同名变量的不同实例,而这些变量在不同线程中不会互相干扰。TLS 在多线程环境中提供了一种为每个线程分配私有存储空间的机制。

简单来说可以理解为 :

  • TLS中的数据像线程的私有全局变量, 避免在各种函数间频繁传入常用的数据降低效率.

在 POSIX 系统中,如果你使用 pthread_key_create() 来创建线程局部存储(TLS)键并且关联某些数据,那么你还需要提供一个销毁函数,这个销毁函数会在线程退出时自动调用,用来释放线程局部存储的数据。

销毁过程:

  • pthread_key_create() 时,你可以指定一个销毁函数(destructor),这个函数会在线程退出时被调用。
  • pthread_key_delete() 用于显式地删除一个 TLS 键,销毁相关的资源。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【开关电源】汽车前端电源保护电路设计

前言&#xff1a; 汽车电池端子在启动或者保养过程中被反接&#xff0c;如果对这些故障不能及时处理&#xff0c;就可能导致ECU或供电设备被损坏&#xff1b;此外在供电过程中电压也存在不稳定的情况。在EMC测试中ISO16750和ISO7637也会有负电压的情况。 肖特基二极管和 P 沟道…

网络运维学习笔记 017 HCIA-Datacom综合实验01

文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置&#xff08;IP二层VLAN链路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…

C++单例模板类,继承及使用

前言&#xff1a; 单例模式可以参考如下文章&#xff1a; 我的设计模式&#xff0c;单例模式的设计和实现 c 单例模式的模板类 - 川野散人 - 博客园 1.为什么需要单例模板类&#xff1f; 场景问题&#xff1a; 如果需要100个单例类就需要设计100个单例模式&#xff0c;代…

nodejs:vue 3 + vite 作为前端,将 html 填入<iframe>,在线查询英汉词典

向 doubao.com/chat/ 提问&#xff1a; node.js js-mdict 作为后端&#xff0c;vue 3 vite 作为前端&#xff0c;编写在线查询英汉词典 后端部分&#xff08;express js-mdict &#xff09; 详见上一篇&#xff1a;nodejs&#xff1a;express js-mdict 作为后端&#xff…

现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能

现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能&#xff0c;每个人都可以通过手机实时拍照上传到大屏幕上,同时还可以发布留言内容&#xff0c;屏幕上会同步滚动播放展示所有人的照片和留言。相比校传统的照片直播功能更加灵活方便&#xff0c;而…

《FFTformer:基于频域的高效Transformer用于高质量图像去模糊》

paper&#xff1a;2211.12250 GitHub&#xff1a;kkkls/FFTformer: [CVPR 2023] Effcient Frequence Domain-based Transformer for High-Quality Image Deblurring CVPR 2023 目录 摘要 1、介绍 2、相关工作 2.1 基于深度CNN的图像去模糊方法 2.2 Transformer及其在图…

ChātGPT赋能的“SolidWorks工具箱”:重塑3D设计效率新标杆

ChātGPT精心打造的“SolidWorks工具箱”正逐步成为3D设计领域中的一颗璀璨新星&#xff0c;其集高效、便捷与创新于一身&#xff0c;为用户带来了前所未有的设计体验。以下是对这一革命性工具箱的深度剖析与美化呈现&#xff1a; 一、核心功能&#xff1a;重塑设计流程&#x…

基于CNN的FashionMNIST数据集识别3——模型验证

源码 import torch import torch.utils.data as Data from torchvision import transforms from torchvision.datasets import FashionMNIST from model import LeNetdef test_data_process():test_data FashionMNIST(root./data,trainFalse,transformtransforms.Compose([tr…

洛谷P1135多题解

解法1&#xff1a;BFS&#xff0c;有n个节点每个节点最多被访问一次&#xff0c;所以BFS时间复杂度为O(n)。注意ab的特判。 #include<iostream> #include<cstring> #include<queue> using namespace std; const int N 205; int n, a, b; int k[N], s[N]; b…

用AI写游戏3——deepseek实现kotlin android studio greedy snake game 贪吃蛇游戏

项目下载 https://download.csdn.net/download/AnalogElectronic/90421306 项目结构 就是通过android studio 建空项目&#xff0c;改下MainActivity.kt的内容就完事了 ctrlshiftalts 看项目结构如下 核心代码 MainActivity.kt package com.example.snakegame1// MainA…

论文解读 | AAAI'25 Cobra:多模态扩展的大型语言模型,以实现高效推理

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 点击 阅读原文 观看作者讲解回放&#xff01; 个人信息 作者&#xff1a;赵晗&#xff0c;浙江大学-西湖大学联合培养博士生 内容简介 近年来&#xff0c;在各个领域应用多模态大语言模型&#xff08;MLLMs&…

DPVS-3: 双臂负载均衡测试

测试拓扑 双臂模式&#xff0c; 使用两个网卡&#xff0c;一个对外&#xff0c;一个对内。 Client host是物理机&#xff0c; RS host都是虚拟机。 LB host是物理机&#xff0c;两个CX5网卡分别在两个子网。 配置文件 用dpvs.conf.sample作为双臂配置文件&#xff0c;其中…

记一次复杂分页查询的优化历程:从临时表到普通表的架构演进

1. 问题背景 在项目开发中&#xff0c;我们需要实现一个复杂的分页查询功能&#xff0c;涉及大量 IP 地址数据的处理和多表关联。在我接手这个项目的时候,代码是这样的 要知道代码里面的 ipsList 数据可能几万条甚至更多,这样拼接的sql,必然是要内存溢出的,一味地扩大jvm参数不…

架构师面试(六):熔断和降级

问题 在千万日活的电商系统中&#xff0c;商品列表页服务通过 RPC 调用广告服务&#xff1b;经过统计发现&#xff0c;在最近10秒的时间里&#xff0c;商品列表页服务在对广告服务的调用中有 98% 的调用是超时的&#xff1b; 针对这个场景&#xff0c;下面哪几项的说法是正确的…

细说 Java 引用(强、软、弱、虚)和 GC 流程(二)

一、前文回顾 在 细说Java 引用&#xff08;强、软、弱、虚&#xff09;和 GC 流程&#xff08;一&#xff09; 我们对Java 引用有了总体的认识&#xff0c;本文将继续深入分析 Java 引用在 GC 时的一些细节。 还是从我们在前文中提到的引用流程图里说起&#xff0c;这里不清…

【深度学习】Unet的基础介绍

U-Net是一种用于图像分割的深度学习模型&#xff0c;特别适合医学影像和其他需要分割细节的任务。如图&#xff1a; Unet论文原文 为什么叫U-Net&#xff1f; U-Net的结构像字母“U”&#xff0c;所以得名。它的结构由两个主要部分组成&#xff1a; 下采样&#xff08;编码…

ROS2机器人开发--服务通信与参数通信

服务通信与参数通信 在 ROS 2 中&#xff0c;服务&#xff08;Services&#xff09;通信和参数&#xff08;Parameters&#xff09;通信是两种重要的通信机制。服务是基于请求和响应的双向通信机制。参数用于管理节点的设置&#xff0c;并且参数通信是基于服务通信实现的。 1 …

DeepSeek写贪吃蛇手机小游戏

DeepSeek写贪吃蛇手机小游戏 提问 根据提的要求&#xff0c;让DeepSeek整理的需求&#xff0c;进行提问&#xff0c;内容如下&#xff1a; 请生成一个包含以下功能的可运行移动端贪吃蛇H5文件&#xff1a; 要求 蛇和食物红点要清晰&#xff0c;不超过屏幕外 下方有暂停和重新…

Python安全之反序列化——pickle/cPickle

一&#xff0e; 概述 Python中有两个模块可以实现对象的序列化&#xff0c;pickle和cPickle&#xff0c;区别在于cPickle是用C语言实现的&#xff0c;pickle是用纯python语言实现的&#xff0c;用法类似&#xff0c;cPickle的读写效率高一些。使用时一般先尝试导入cPickle&…

Deepin(Linux)安装MySQL指南

1.下载 地址&#xff1a;https://downloads.mysql.com/archives/community/ 2.将文件解压到 /usr/local 目录下 先cd到安装文件所在目录再解压&#xff0c;本机是cd /home/lu01/Downloads sudo tar -xvJf mysql-9.2.0-linux-glibc2.28-x86_64.tar.xz -C /usr/local3.创建软链…