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

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

多线程概念与常用接口


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

什么是多线程(概念)

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

补充:什么是高并发:

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


目录
相关文章
|
2月前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
46 1
|
15天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
20天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
17天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
33 1
|
17天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
24 1
|
22天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4
|
29天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
22天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
36 2
|
24天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3