多线程概念,常用接口与多进程之间的比较

简介: 多线程概念,常用接口与多进程之间的比较

多线程概念与常用接口


多线程概念与相对于线程的区别

什么是多线程(概念)

多线程是指在同一个进程内同时执行多个线程,每个线程都是独立的执行流,都有自己的程序计数器、堆栈、寄存器和状态等信息,但共享同一个进程的地址空间和资源(在进程的共享区)。多线程可以提高程序的并发性和响应速度,使得程序能够更加有效地利用计算机的多核心和多任务处理能力。

补充:什么是高并发:

高并发性是指系统能够同时处理大量的并发请求,保持系统的高效性和可用性。 在计算机领域,高并发性通常指在单位时间内处理的请求量非常大,例如每秒处理几百万甚至几千万个请求。高并发性通常是对于网络服务、Web应用程序、数据库系统、操作系统等需要处理大量请求的系统而言的。


进程和线程的区别

进程是计算机中正在执行的程序实例,是资源分配的单位。当程序被启动时,操作系统为该程序分配一段内存空间,这段内存空间包含了程序的代码、数据和堆栈等信息,并为该程序创建一个进程实例。进程包括了程序的所有状态,例如程序计数器、寄存器、堆栈指针和打开的文件等。操作系统利用进程的信息来控制程序的执行和资源的分配,使多个程序可以同时在计算机上运行。


进程是计算机中最基本的执行单元,每个进程都拥有自己的内存空间、资源和执行上下文,这使得多个进程可以同时在计算机上运行,而不会相互干扰。进程之间可以通过进程间通信来进行数据交换和协作,


线程是进程中的执行单元,每个进程可以包含多个线程。 线程共享进程的内存空间和资源,每个线程拥有自己的执行上下文和栈。线程可以协作完成一个任务,也可以同时执行多个任务,这使得多线程程序可以充分利用计算机的多核处理能力,提高程序的性能和响应速度。


因此,进程和线程都是计算机中的执行单元,进程是操作系统中资源分配的最小单位,线程是进程中的执行单元,一个进程可以包含多个线程。两者的区别在于进程是操作系统中的基本单位,而线程是进程中的执行单元,进程之间相互独立,线程之间共享进程的资源和上下文。


在Linux系统下,进程和线程的区别如下:

1.资源占用:每个进程都会占用一定的系统资源,包括独立的地址空间、文件描述符、内存和系统调用等,而线程则共享进程的资源,包括地址空间、文件描述符、内存和系统调用等。

2.上下文切换:进程之间的切换需要进行完整的上下文切换,包括寄存器、内存和I/O等,而线程之间的切换只需要切换线程的上下文和栈,比进程切换更快。

3.调度:进程之间的调度由操作系统负责,每个进程有自己的调度策略和优先级,而线程之间的调度由进程内的线程库负责,根据线程的优先级和调度算法来调度线程。

4.通信:进程之间的通信需要使用进程间通信机制(IPC),例如管道、信号、共享内存和消息队列等,而线程之间的通信可以直接通过共享内存和进程内部的信号量、互斥锁和条件变量等进行通信。

5.安全性:进程之间的数据隔离更加彻底,可以提供更高的安全性,而线程之间共享同一个地址空间,可能会出现数据互相干扰的问题,需要进行同步和互斥等操作来保证数据的一致性和安全性。

因此,在Linux系统下,进程和线程都是执行单元,进程是资源分配的基本单位,线程是进程中的执行单元。 进程之间的隔离更加彻底,线程之间的切换更加快速,但需要考虑线程间的同步和互斥问题。

在Linux下,进程和线程都是通过pcb实现的,进程和线程都是以任务(task)的形式存在的,每个任务都对应着一个PCB,任务的切换实际上就是PCB的切换。由于线程共享同一个进程的地址空间和资源,因此Linux中线程的PCB与进程的PCB在结构上是有所区别的,线程的PCB通常称为线程描述符(Thread Descriptor)或轻量级进程(LWP,Light-weight Process)。Linux中线程的PCB与进程的PCB在结构上的区别在于,线程的PCB只记录了线程的部分状态,如堆栈指针、寄存器值、调度策略等,而没有单独的地址空间和文件描述符等资源,这些资源是共享给整个进程的。因此,线程切换的代价相对进程切换要小得多,这也是多线程应用程序的一个重要优势。

多进程和多线程优缺点比较:

多进程和多线程都是用来实现并发的技术,但它们各自具有一些优缺点,下面对它们进行比较:

多进程的优点:

稳定性(健壮性强):每个进程都是独立的,一个进程崩溃不会影响其他进程,因此系统的稳定性更高。

安全性:进程间的内存空间是独立的,不同进程之间的数据不会互相干扰,因此系统的安全性更高。

可扩展性:可以利用多台计算机的资源,将多个进程分配到不同的计算机上运行,从而实现更高的可扩展性。

所以在例如网络服务器,shell程序这种对于主程序要求高的情况,多使用多进程,其他情况尝尝使用多线程提高效率。

多进程的缺点:

资源消耗:每个进程都需要独立的地址空间、文件描述符、信号处理等资源,因此创建和维护多个进程的开销相对较大。

进程间通信:进程间通信需要额外的开销,而且往往需要使用复杂的通信机制来实现。

上下文切换:进程切换的开销相对较大,需要保存和恢复更多的状态信息,因此系统的性能可能受到影响。

多线程的优点:

资源节约:多个线程可以共享同一个进程的地址空间、文件描述符、信号处理等资源,因此创建和维护多个线程的开销相对较小

响应速度:线程切换的开销相对较小,可以更快地响应用户的请求,提高系统的响应速度。

简单易用:线程的创建,销毁和管理相对较简单,程序员可以更方便地利用多线程实现并发操作。

多线程的缺点:

稳定性:多个线程共享同一个地址空间,一个线程的错误可能会影响其他线程或整个进程,因此系统的稳定性可能会受到影响。

安全性:多个线程共享同一个内存空间,数据竞争和死锁等问题可能会出现,程序员需要使用同步机制来保证线程的安全性。

调试困难:多个线程同时运行,程序的执行流程可能会变得复杂和难以调试,特别是在多线程并发环境下,程序员需要花费更多的时间和精力来调试程序。

在多任务处理中,启动多少执行流比较合适

在多任务处理中,使用多执行流(多线程)可以更加充分的利用计算机资源,提高执行效率

但是执行流越多,cpu切换调度就越频繁,如果执行流太多,返回会造成切换调度消耗了大部分的资源。在任务处理中,程序一般分为俩种程序:


cpu密集型程序:一段程序中几乎都是数据的运算(对cpu的使用率非常高)

IO密集型程序:一段程序中大部分是IO操作(大部分时间都是在进行IO操作以及等待,因此对CPU使用率并不高)

因此,不同的程序对于CPU的要求是不一样的,因此多执行流没有什么固定的数量,最好是经过压力测试找到最合适的数量

线程控制

主要介绍的是线程的接口,其实都是大佬们封装的库函数,因为linux操作系统并没有直接向上层提供线程系统调用接口。

在linux下创建一个线程,其实就是创建一个pcb,多个pcb指向同一个虚拟空间,因此上层接口就要实现找到pcb并对其进行一些控制


线程的创建

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);


参数thread是一个指向pthread_t类型变量的指针,用于存储新线程的ID(线程的操作句柄);

参数attr是一个指向pthread_attr_t类型变量的指针,用于指定新线程的属性,如果为NULL,则使用默认属性;

参数start_routine是一个指向函数的指针(函数指针),该函数是新线程的启动函数,即线程的入口函数,这个线程调度运行的就是这个函数,我们知道线程就是控制进程中的一部分,所以线程就是控制调度这一部分函数;

参数arg是传递给启动函数(rounite)的参数。

功能即:创建一个线程,指定这个线程运行的函数routine,并且给这个函数传入一个数据arg,成功返回0,不成功返回非0值。


操作演示:


image.pngimage.pngimage.png

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
void *thread_entry(void* arg){
    while (1)
    {
        printf("i am ordinary thread:%s\n",arg);
    }
}
int main()
{
    pthread_t tid;
    void * arg = "hello";
    int ret =  pthread_create(&tid,NULL,thread_entry,arg);
    if(ret!=0){
        printf("pthread error\n");
        return -1;
    }
    while(1){
        printf("i am main thread\n");
    }
    return 0;
}

普通线程一旦创建出来,创建出来的这个线程调度的是传入线程的入口函数,因此主进程也可以走下来,不等普通线程结束,并发进行。创建一个线程,其实就是让操作系统提供一个执行流。至于让线程做什么,取决于它的入口函数。


一个执行流在cpu运行的时间是一个时间片,谁先运行不一定,取决于操作系统的调度。主进程和子线程是并发执行的,它们的执行顺序是由系统调度器决定的。在多线程程序中,所有线程都可以并行运行,因此不能保证主进程先于所有子线程执行完毕。如果需要等待所有子线程执行完毕后再退出主进程,可以使用pthread_join()等待线程退出。


线程的终止

线程其实调度运行的是线程入口函数传入的入口函数,因此其实程序线程入口函数退出了,线程也就退出了。


在线程入口函数中return

注意:main中return退出的不仅仅是主线程,而是整个线程(退出了进程)

调用pthread_exit(void *retval):接口退出,retval是线程退出的返回值

上边两个是主动退出,在任意地方主动调用,主动调用,谁调用谁退出

调用void pthread_cancel(pthread_t thread)接口,该函数用于向指定线程发送取消请求,如果一个线程被取消,则不能调用其返回值进行操作(比如获取退出码进行线程等待,没有实际的处理意义)。假设我现在在主线程运行三秒后让普通线程退出,我就可以使用一个计时器,在主线程运行三秒后,调用接口让普通线程取消。

线程的等待

主线程退出,其实并不会影响其他线程的运行,一个线程被释放之后,它所占用的资源并不会立即释放。相反,线程的资源会在该线程结束后被释放。


当一个线程调用pthread_exit()函数或者线程函数执行完毕退出时,该线程会被标记为“已终止”,但是它所占用的资源并不会立即释放。线程的资源包括堆栈、寄存器、线程控制块等等。这些资源会被操作系统回收,但是具体的时间是不确定的,取决于操作系统的实现。通常,操作系统会在一段时间后回收线程资源,以确保资源能够被充分利用。


需要注意的是,如果一个线程没有被正确地销毁,即没有调用pthread_join()或pthread_detach()函数来回收线程资源,那么线程的资源将一直存在,直到进程结束。这会导致内存泄漏等问题,因此在编写多线程程序时需要特别注意线程的正确销毁和资源回收。


如果主线程调用pthread_exit()或者return语句退出,那么它所创建的其他线程将会继续运行,直到它们自己结束或被取消。但是如果主线程通过调用exit()函数、abort()函数或者收到信号等方式退出,那么整个进程将会被终止,所有线程都将被强制结束,即使它们没有执行完毕。这种情况下,其他线程的状态和数据都将丢失,可能会导致数据不一致或资源泄漏等问题。


等待:等待指定线程退出,获取退出的线程返回值,回收退出线程的所有资源


pthread_join(pthread_t thread, void **retval):该函数用于等待指定线程的结束,并获取线程的返回值。调用线程会一直阻塞,直到指定线程退出为止。如果不需要获取线程的返回值,返回的是一个地址,所以传入二级指针。可以将retval参数设置为NULL。thread是获取线程的tid。被等待的线程在退出时必须通过pthread_exit()函数返回一个值,否则无法获取到有效的返回值。成功返回0,失败返回一个错误编号。


pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex):该函数用于等待条件变量的触发,并在等待期间释放互斥锁。在调用该函数之前,必须先获得互斥锁,否则会导致未定义的行为。当条件变量被触发时,该函数会重新获得互斥锁,并继续执行。


注意:线程的传递需要额外的注意数据的常量。(static,malloc创建的等),如果线程被取消了,将会返回一个宏。


线程的分离

线程的分离(Detached Thread)是指将一个线程设置为分离状态,使得该线程结束后,它所占用的资源会自动被系统回收,而无需其他线程调用pthread_join()函数来获取其退出状态。在线程属性中,有一个属性叫做分离属性,默认是joinable状态,表示线程退出后,不会被自动释放资源,需要被其他线程等待,但是我们有时候并不关心一个线程的返回值,也不想等待他的退出,则这个时候会将这个分离属性设置为detach状态,表示线程退出后,会自动释放所有资源,不需要被等待(等待会出错)

int pthread_detach(pthread_t tid);//设置指定线程的分离属性为detach


目录
相关文章
|
11天前
|
UED 开发者 Python
探索操作系统的心脏:理解进程与线程
【8月更文挑战第31天】在数字世界的海洋中,操作系统犹如一艘巨轮,其稳定航行依赖于精密的进程与线程机制。本文将揭开这一机制的神秘面纱,通过深入浅出的语言和直观的代码示例,引领读者从理论到实践,体验进程与线程的魅力。我们将从基础概念出发,逐步深入到它们之间的联系与区别,最后探讨如何在编程实践中高效运用这些知识。无论你是初学者还是有经验的开发者,这篇文章都将为你的技术之旅增添新的航标。
|
17天前
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
32 1
|
16天前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
4天前
|
存储 Java 数据处理
进程中的线程调度
进程是应用程序运行的基本单位,包括主线程、用户线程和守护线程。计算机由存储器和处理器协同操作,操作系统设计为分时和分任务模式。在个人PC普及后,基于用户的时间片异步任务操作系统确保了更好的体验和性能。线程作为进程的调度单元,通过覆写`Thread`类的`run`方法来处理任务数据,并由系统调度框架统一管理。微服务架构进一步将应用分解为多个子服务,在不同节点上执行,提高数据处理效率与容错性,特别是在大规模数据存储和处理中表现显著。例如,利用微服务框架可以优化算法,加速业务逻辑处理,并在不同区块间分配海量数据存储任务。
|
13天前
|
Java
在Java多线程领域,精通Lock接口是成为高手的关键。
在Java多线程领域,精通Lock接口是成为高手的关键。相较于传统的`synchronized`,Lock接口自Java 5.0起提供了更灵活的线程同步机制,包括可中断等待、超时等待及公平锁选择等高级功能。本文通过实战演练介绍Lock接口的核心实现——ReentrantLock,并演示如何使用Condition进行精确线程控制,帮助你掌握这一武林秘籍,成为Java多线程领域的盟主。示例代码展示了ReentrantLock的基本用法及Condition在生产者-消费者模式中的应用,助你提升程序效率和稳定性。
17 2
|
13天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
16 2
|
14天前
|
Java
多线程同步新姿势:Lock接口助你“一统江湖”!
多线程同步新姿势:Lock接口助你“一统江湖”!
37 2
|
14天前
|
调度
深入理解操作系统:进程与线程的管理
【8月更文挑战第29天】在数字世界的每一次点击和滑动背后,都隐藏着操作系统的精妙运作。本文将带你探索操作系统的核心概念之一——进程与线程的管理。我们将从基础定义出发,逐步深入到它们在内存中的表示、状态变迁以及它们之间错综复杂的关系。通过简洁明了的语言和直观的比喻,即便是没有计算机背景的读者也能轻松理解这一主题。准备好了吗?让我们一起揭开操作系统神秘的面纱,探索那些看似晦涩却无比精彩的知识吧!
|
15天前
|
调度 Python
深入理解操作系统:进程与线程的奥秘
【8月更文挑战第27天】本文将带你走进操作系统的核心,探索进程和线程这两个基本概念。我们将从它们的定义开始,逐步深入到它们之间的联系和区别,以及在操作系统中的作用。通过本文,你将了解到进程和线程不仅仅是编程中的两个术语,它们是操作系统管理资源、实现并发和并行的关键。最后,我们还将通过一个代码示例,展示如何在Python中创建和管理线程。
|
14天前
|
算法 调度 开发者
深入理解操作系统:进程与线程管理
【8月更文挑战第28天】在数字世界的心脏跳动着的是操作系统,它是计算机硬件与软件之间的桥梁。本文将带你探索操作系统的核心概念——进程与线程,揭示它们如何协同工作以支持多任务处理和并发执行。通过实际代码示例,我们将深入了解这些抽象概念是如何在真实系统中实现的。无论你是编程新手还是资深开发者,这篇文章都将为你提供新的视角,让你对操作系统有更深刻的认识。