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

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

多线程概念与常用接口


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

什么是多线程(概念)

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

补充:什么是高并发:

高并发性是指系统能够同时处理大量的并发请求,保持系统的高效性和可用性。 在计算机领域,高并发性通常指在单位时间内处理的请求量非常大,例如每秒处理几百万甚至几千万个请求。高并发性通常是对于网络服务、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


目录
相关文章
|
18天前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
35 1
|
1天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
4天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
19 4
|
10天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
4天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
13 2
|
5天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
30天前
|
存储 消息中间件 人工智能
进程,线程,协程 - 你了解多少?
本故事采用简洁明了的对话方式,尽洪荒之力让你在轻松无负担的氛围中,稍微深入地理解进程、线程和协程的相关原理知识
40 2
进程,线程,协程 - 你了解多少?
|
14天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
14天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
27 2
|
14天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2