【Linux操作系统】线程的基本知识和创建--循环创建多个子线程

news2024/9/25 19:23:20

本篇文章主要介绍了线程的概念和作用,线程三级映射的实现,创建线程的方法(讲解pthread_self和pthread_create函数),循环创建多个子线程为例子,同时分析线程之间的全局变量的共享问题,希望可以帮助你。

在这里插入图片描述

文章目录

  • 一、线程的概念及其作用
    • 作用:
    • 三级映射
  • 二、线程的共享和非共享
    • 共享线程
    • 非共享线程
  • 三、创建线程的方法
    • 相关函数
      • 1. `pthread_self()`:
      • 2. `pthread_create`:
    • 继承Thread类
    • 实现Runnable接口
  • 四、循环创建多个子线程
    • 步骤
    • 示例
    • 代码解释
  • 线程间全局变量共享


一、线程的概念及其作用

在计算机科学中,线程是进程中的一个执行单元。一个进程可以拥有多个线程,每个线程都可以独立执行不同的任务。线程是实现并发执行的重要工具,可以提高程序的效率和性能。

作用:

  1. 提高程序的响应性:通过多线程可以同时执行多个任务,提高程序的响应速度,避免因为一个任务的阻塞而导致整个程序的停顿。

  2. 提高计算机资源的利用率:多线程可以充分利用计算机的多核处理器,同时执行多个任务,提高计算机资源的利用率。

  3. 实现并发编程:线程可以用于实现并发编程,例如在服务器中同时处理多个客户端请求,或者在图形界面程序中同时响应用户的输入等。

  4. 实现任务的分解和协作:通过将一个复杂任务分解为多个子任务,每个子任务由一个线程执行,可以提高程序的可维护性和可扩展性。线程之间可以通过共享内存或消息传递等方式进行协作和通信。

在这里插入图片描述


三级映射

在操作系统中,线程的运行需要通过三级映射来实现。这三级映射包括虚拟地址到物理地址的映射、物理地址到主存的映射、以及主存到磁盘的映射。

  1. 虚拟地址到物理地址的映射:每个线程都有自己的虚拟地址空间,虚拟地址是线程在运行时使用的地址。操作系统通过页表来实现虚拟地址到物理地址的映射。页表将虚拟地址划分为一系列的页面,并将这些页面映射到物理地址上的页框。当线程访问虚拟地址时,操作系统会根据页表将虚拟地址转换为物理地址。

  2. 物理地址到主存的映射:物理地址是实际的硬件地址,表示计算机中的内存地址。操作系统管理物理地址和主存之间的映射关系,确保线程可以访问到正确的主存地址。操作系统会将物理地址分配给不同的线程,以便线程可以在主存中存储和访问数据。

  3. 主存到磁盘的映射:主存是计算机中的临时存储器,用于存储线程运行时所需的数据和指令。但是主存的容量是有限的,当主存不足以存储所有线程需要的数据时,操作系统会将部分数据存储到磁盘上的虚拟内存中。这样,当线程需要访问被存储在磁盘上的数据时,操作系统会将数据从磁盘读取到主存中,然后线程可以继续访问这些数据。


二、线程的共享和非共享

共享线程

共享线程是指多个线程可以访问和修改同一进程的共享资源。共享线程通常用于多个线程之间需要共享数据和进行协作的场景。

在共享线程中,多个线程可以同时读取和修改同一份共享数据。这种共享数据可以是 全局变量、静态变量、堆内存中的对象等 。共享线程可以通过对共享资源的读写操作来实现线程之间的通信和协作。

共享线程的优点是可以方便地共享数据和协作,但同时也需要注意线程安全的问题。多个线程同时读写共享资源可能会导致数据的不一致性和竞态条件等问题,需要通过加锁、使用同步机制等方式来保证共享资源的正确性和一致性。

非共享线程

非共享线程是指每个线程拥有自己独立的资源,其他线程无法访问和修改。非共享线程通常用于执行独立的任务,不需要与其他线程进行通信和共享数据。

在非共享线程中,每个线程拥有自己独立的 栈空间、寄存器 等资源。这意味着每个线程都可以独立地执行自己的任务,互不干扰。非共享线程的优点是可以提高程序的并发性和执行效率,但同时也限制了线程之间的通信和协作。


三、创建线程的方法

相关函数

我们先来认识两个关于创建线程的函数:pthread_selfpthread_create


1. pthread_self():

pthread_self函数作用是获取当前线程的线程ID

函数原型:

pthread_t pthread_self(void);

该函数没有参数。

返回值:
返回调用线程的线程ID。


2. pthread_create

pthread_create函数的作用是创建一个新的线程,并将其加入到进程中。通过该函数,我们可以实现多线程的并发执行。

函数原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

参数:

  • thread:指向线程ID的指针,用于存储新创建的线程的ID。
  • attr:指向线程属性的指针,用于设置新创建的线程的属性。可以传入NULL,使用默认属性。
  • start_routine:指向线程函数的指针,新创建的线程将从该函数开始执行。
  • arg:传递给线程函数的参数,可以是任意类型的指针。

返回值:
成功创建线程时,返回0;创建线程失败时,返回一个非零的错误代码。

示例和解释:

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

void* thread_func(void* arg) {
    int thread_num = *(int*)arg;
    printf("Thread %d is running\n", thread_num);
    return NULL;
}

int main() {
    pthread_t thread1, thread2;
    int arg1 = 1, arg2 = 2;

    pthread_create(&thread1, NULL, thread_func, &arg1);
    pthread_create(&thread2, NULL, thread_func, &arg2);

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

    return 0;
}

在上述示例中,我们定义了一个线程函数thread_func,该函数接收一个整数参数,并将其打印出来。在main函数中,我们创建了两个线程,并分别传递不同的参数给它们。通过pthread_create函数,我们将线程函数和参数传递给新创建的线程。然后,我们使用pthread_join函数等待这两个线程执行完成。

pthread_create函数用于创建一个新线程,并将其加入到进程中。该函数需要传入线程ID指针、线程属性、线程函数和参数。成功创建线程后,新线程将从指定的线程函数开始执行,并且可以访问传递给线程函数的参数。在上述示例中,我们可以看到两个线程分别打印了传递给它们的参数,说明它们成功接收到了参数并进行了相应的处理。


在大多数编程语言中,创建线程通常有两种方法:继承Thread类和实现Runnable接口。

继承Thread类

继承Thread类是一种创建线程的简单方法。通过继承Thread类,可以重写run()方法来定义线程的执行逻辑。

以下是一个使用继承Thread类创建线程的示例:

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

// 继承Thread类,重写run()方法
void* thread_func(void* arg) {
    // 线程的执行逻辑
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    // 创建线程实例
    pthread_t thread;

    // 启动线程
    pthread_create(&thread, NULL, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread, NULL);

    return 0;
}

在上述示例中,我们通过继承Thread类,重写了run()方法,并在其中定义了线程的执行逻辑。然后使用pthread_create()函数创建了一个线程实例,并通过pthread_join()函数等待线程执行完成。

实现Runnable接口

实现Runnable接口是另一种创建线程的常用方法。通过实现Runnable接口,可以将线程的执行逻辑定义在run()方法中。

以下是一个使用实现Runnable接口创建线程的示例:

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

// 实现Runnable接口,定义run()方法
void* thread_func(void* arg) {
    // 线程的执行逻辑
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    // 创建线程实例
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 创建线程对象
    pthread_create(&thread, &attr, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread, NULL);

    return 0;
}

在上述示例中,我们通过实现Runnable接口,定义了run()方法,并在其中定义了线程的执行逻辑。然后使用pthread_create()函数创建了一个线程实例,并通过pthread_join()函数等待线程执行完成。


四、循环创建多个子线程

步骤

  1. 定义一个线程函数,该函数包含需要在子线程中执行的逻辑。可以在该函数中使用线程参数来区分不同的子线程。

  2. 在主函数中,使用pthread_create函数循环创建多个子线程。每个子线程都调用同一个线程函数。

  3. pthread_create函数中,将线程函数作为线程函数指针传递,并将对应的参数传递给线程函数。可以使用结构体或指针等方式传递参数。

  4. 在线程函数中,根据传递的参数执行相应的逻辑。

示例

下面是一个使用C语言实现的示例代码,演示了如何循环创建多个子线程,每个子线程都继承自同一个线程函数的方法:

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

// 线程函数
void* threadFunc(void* arg) {
    int thread_num = *(int*)arg;
    printf("Thread %d is running\n", thread_num);
    // 执行线程的逻辑
    // ...
    return NULL;
}

int main() {
    const int NUM_THREADS = 5;
    pthread_t threads[NUM_THREADS];
    
    for (int i = 0; i < NUM_THREADS; i++) {
        int* thread_arg = malloc(sizeof(int));
        *thread_arg = i + 1;
        pthread_create(&threads[i], NULL, threadFunc, thread_arg);
    }
    
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}

代码解释

在上述示例中,我们定义了一个线程函数threadFunc,其中包含了需要在子线程中执行的逻辑。在主函数中,我们使用pthread_create函数循环创建了5个子线程。每个子线程都调用threadFunc函数,并将对应的参数传递给线程函数。在threadFunc函数中,我们根据传递的参数执行线程的逻辑。


线程间全局变量共享

在多线程编程中,线程间的数据共享是一个常见的问题。线程间可以通过共享内存或全局变量来实现数据的共享。

以下是一个使用全局变量实现线程间数据共享的示例:

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

// 全局变量
int count = 0;

// 实现Runnable接口,定义run()方法
void* thread_func(void* arg) {
    // 访问和修改全局变量
    count++;
    return NULL;
    int main() {
    // 创建线程实例
    pthread_t thread1, thread2;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 创建线程对象
    pthread_create(&thread1, &attr, thread_func, NULL);
    pthread_create(&thread2, &attr, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 打印全局变量的值
    printf("Count: %d\n", count);

    return 0;
}

在上述示例中,我们使用全局变量count来实现线程间的数据共享。在每个线程中,我们递增count的值。然后在主线程中打印count的值。由于count是全局变量,所有线程都可以访问和修改它,因此最终打印的count的值可能不是预期的结果。

在这里插入图片描述

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

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

相关文章

fastjson-1.2.24-rce(CVE-2017-18349)fastjson-1.2.47-rce(CNVD-2019-22238)

一.fastjson 1.2.24 反序列化导致任意命令执行漏洞(CVE-2017-18349) fastjson在解析json的过程中&#xff0c;支持使用autoType来实例化某一个具体的类&#xff0c;并调用该类的set/get方法来访问属性。通过查找代码中相关的方法&#xff0c;即可构造出一些恶意利用链 影响范围…

LeetCode 刷题第四轮 Offer I + 类型题

目录 剑指 Offer 04. 二维数组中的查找 剑指 Offer 29. 顺时针打印矩阵 剑指 Offer 09. 用两个栈实现队列 剑指 Offer 30. 包含min函数的栈 剑指 Offer 10- I. 斐波那契数列 [类型&#xff1a;记忆优化 递归 / 动态规划] 剑指 Offer 10- II. 青蛙跳台阶问题 [类型&am…

AcWing算法提高课-5.5.2最大公约数

宣传一下 算法提高课整理 CSDN个人主页&#xff1a;更好的阅读体验 原题链接 题目描述 给定整数 N N N&#xff0c;求 1 ≤ x , y ≤ N 1 \le x,y \le N 1≤x,y≤N 且 gcd ⁡ ( x , y ) \gcd(x,y) gcd(x,y) 为素数的数对 ( x , y ) (x,y) (x,y) 有多少对。 输入格式 输…

pandas由入门到精通-数据清洗-缺失值处理

pandas-02-数据清洗&预处理 A.缺失值处理1. Pandas缺失值判断2. 缺失值过滤2.1 Series.dropna()2.2 DataFrame.dropna()3. 缺失值填充3.1 值填充3.2 向前/向后填充文中用S代指Series,用Df代指DataFrame 数据清洗是处理大型复杂情况数据必不可少的步骤,这里总结一些数据清…

数字基带传输系统

文章目录 前言一、数字基带系统基本组成二、基本码型1、数字基带信号2、6 种基本码型 三、数字基带信号的频谱特性四、数字基带信号选码1、原则2、常用的传输码型①、AMI 码&#xff08;传号交替反转码&#xff09;②、 H D B 3 HDB_3 HDB3​ 码&#xff08;3 阶高密度双极性码…

GeoHash之存储篇

前言&#xff1a; 在上一篇文章GeoHash——滴滴打车如何找出方圆一千米内的乘客主要介绍了GeoHash的应用是如何的&#xff0c;本篇文章我想要带大家探索一下使用什么样的数据结构去存储这些Base32编码的经纬度能够节省内存并且提高查询的效率。 前缀树、跳表介绍&#xff1a; …

京东话费直充系统——轻松充值移动、联通、电信三大运营商的通话套餐

京东三网话费直充系统/移动联通电信话费三网直充/三网话费直充系统 系统特性: ①、移动&#xff0c;联通&#xff0c;电信话费使用微信H5/支付宝H5 ②、移动话费/联通话费/电信话费额度支持1-任意额度&#xff08;不得超过官网所支持的额度&#xff09; ③、系统实测每分钟…

pandas由入门到精通-数据透视表

采集的数据存储后通常会分为多个文件或数据库,如何将这些文件按需拼接,或按键进行连接十分重要。这节将介绍数据索引的复杂操作如分层索引,stack,unstack,seet_index,reset_index等帮助重构数据,数据的拼接如merge,join,concat,combine_first等帮助连接数据,以及数据透视表…

全球首家?梅赛德斯-奔驰将在成都启动超级充电站,功率高达480kW

根据官方宣布的消息&#xff0c;梅赛德斯-奔驰将在中国启动充电功率高达480kW的首批超级充电站建设项目。这些超级充电站将成为全球首家梅赛德斯-奔驰品牌的充电站&#xff0c;并计划于今年10月在成都投入使用。 除了成都&#xff0c;这些充电站还将陆续在包括北京、深圳等其他…

AI 绘画Stable Diffusion 研究(十七)SD lora 详解(上)

大家好&#xff0c;我是风雨无阻。 本期内容&#xff1a; Lora的原理是什么&#xff1f;Lora如何下载安装&#xff1f;Lora如何使用&#xff1f; 大家还记得 AI 绘画Stable Diffusion 研究&#xff08;三&#xff09;sd模型种类介绍及安装使用详解 这篇文章中&#xff0c;曾简…

2022年12月 C/C++(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题:开餐馆 北大信息学院的同学小明毕业之后打算创业开餐馆.现在共有n 个地点可供选择。小明打算从中选择合适的位置开设一些餐馆。这 n 个地点排列在同一条直线上。我们用一个整数序列m1, m2, … mn 来表示他们的相对位置。由于地段关系,开餐馆的利润会有所不同。我们用pi …

DELL Power Edge R740 安装 OracleLinux-R7-U9-Server

一、准备好 OracleLinux-R7-U9-Server-x86_64-dvd 安装介子&#xff1a; 二、通过 iDRAC挂dvd 安装介子 三、在 iDRAC 开机控制选择虚拟 CD/DCD/ISO 电源控制选择 复位系统&#xff08;热启动&#xff09; 四、进入安装阶段 五、配置时区 六、配置磁盘 七、删除之前的旧分区 …

力与美的交响丨远航Y6全国媒体试驾会成都举办,实力演绎中式豪华+极致性能

8月24日&#xff0c;四川成都&#xff0c;远航Y6全国媒体试驾会圆满举办。来自全国的近百家媒体亲身试乘试驾&#xff0c;深度感受远航Y6诠释的高端豪华新体验。 绵绵细雨难掩相聚的热情。远航汽车自去年成都车展正式发布亮相&#xff0c;就与成都结下了不解之缘。历经一年的持…

Nginx全局配置

一、修改启动进程数 worker_processes 1; #允许的启动工作进程数数量&#xff0c;和你真实的cpu数量有关 1 worker_processes auto; #如果设置为auto 就是你真实的cpu数量 ps axo pid,cmd,psr,ni|grep nginx #可以看到 nginx的 worker数量 二、日制分割 [rootyuji ~]#…

C语言小练习(五)

&#x1f31e; 忘掉那些“不可能”的借口吧&#xff0c;去坚持一个“可能”的理由&#xff0c;请给时间一点时间&#xff0c;让开始开始&#xff0c;只要肯努力&#xff0c;总会成功的&#xff01; Day05 &#x1f4dd; 选择题 &#x1f4dd; 选择题 &#x1f388;1、如下程序…

IA-YOLO项目中DIP模块的初级解读

IA-YOLO项目源自论文Image-Adaptive YOLO for Object Detection in Adverse Weather Conditions&#xff0c;其提出端到端方式联合学习CNN-PP和YOLOv3&#xff0c;这确保了CNN-PP可以学习适当的DIP&#xff0c;以弱监督的方式增强图像检测。IA-YOLO方法可以自适应地处理正常和不…

构建数据可视化(基于Echarts,python)

构建数据可视化&#xff08;基于Echarts,python) 本文目录&#xff1a; 一、写在前面的题外话 二、数据可视化概念 三、用Python matplotlib库绘制数据可视化图 四、基于Echarts构建大数据可视化 4.1、安装echarts.js 4.2、数据可视化折线图制作 4.2.1、基础折线图 4.2…

安全开发-JS应用NodeJS指南原型链污染Express框架功能实现审计WebPack打包器第三方库JQuery安装使用安全检测

文章内容 环境搭建-NodeJS-解析安装&库安装安全问题-NodeJS-注入&RCE&原型链案例分析-NodeJS-CTF题目&源码审计打包器-WebPack-使用&安全第三方库-JQuery-使用&安全 环境搭建-NodeJS-解析安装&库安装 Node.js是运行在服务端的JavaScript 文档参考…

3张图打造个人写真照,你也可以AIGC

前阵子&#xff0c;妙鸭相机的出现&#xff0c;9.9的价格&#xff0c;让大家过了一把写真照的瘾。 最近自己也开始关注一些大数据模型相关的内容&#xff0c;有时会想想&#xff0c;有没哪些比较好的模型&#xff0c;能够运用在手机移动端。然后我们实际生活中又会有哪些应用需…

【C++】map的奇葩用法:和函数结合

2023年8月26日&#xff0c;周六下午 今天才发现map居然还能这样用... #include <iostream> #include <map> #include <functional>void printOne() {std::cout << "已经打印出1" << std::endl; }void printTwo() {std::cout <<…