苏州网站设计网站搭建,吉林省建设厅官网查询,品牌建设总结,建网站的尺寸前面学习了进程、文件等概念#xff0c;接下里为大家引入线程的概念 多线程 线程是什么#xff1f;为什么要有线程#xff1f;线程的优缺点Linux线程操作线程创建线程等待线程终止线程分离 线程间的私有和共享数据理解线程库和线程id深刻理解Linux多线程#xff08;重点接下里为大家引入线程的概念 多线程 线程是什么为什么要有线程线程的优缺点Linux线程操作线程创建线程等待线程终止线程分离 线程间的私有和共享数据理解线程库和线程id深刻理解Linux多线程重点 线程是什么 线程是一个执行分支执行粒度比进程更细调度成本更低。线程是进程内部的一个执行流线程是CPU调度的基本单位进程是承担分配系统资源的基本实体 为什么要有线程 多线程编程在现代计算中具有显著的优势主要体现在以下几个方面 发挥多核CPU优势 现代处理器普遍拥有多个核心多线程能够确保这些核心得到充分利用。通过创建多个线程程序可以同时在不同核心上执行不同的任务部分从而提升并行处理能力整体提高程序的运行效率。防止阻塞和提高响应性 当一个线程在等待IO操作如磁盘读写、网络通信或执行耗时计算时操作系统可以调度其他线程继续执行避免了整个进程停滞提高了系统的响应速度。并发处理能力增强 多线程使得系统能够并发地处理多个用户请求或执行多个独立的任务。这对于高并发场景下的服务器应用、实时数据处理、大规模并行计算等尤为关键可以显著提升系统吞吐量和服务质量。模块化与简化设计 在复杂的软件架构中多线程有助于将大的任务分解成若干个可管理的子任务并分配给不同的线程来处理。这不仅使得代码逻辑更加清晰也更容易进行模块化开发和维护。改善用户体验 用户界面应用程序中主线程负责处理UI事件后台线程则可以处理长时间运行的操作例如加载数据、文件压缩解压。这样可以在不影响用户界面交互的同时完成大量工作提升了用户体验。资源利用率 相对于为每个并发任务创建新的进程线程间的切换开销较小因为它们共享同一进程的地址空间和其他资源。这降低了系统资源消耗尤其是在内存有限的情况下。 然而多线程编程也带来了一系列挑战包括但不限于资源共享引发的数据竞争问题、死锁、优先级反转等并发控制难题。因此在享受多线程带来的性能提升时开发者需要精心设计和实现线程间同步机制以保证程序的正确性和稳定性。
线程的优缺点 优点 创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作。 缺点 性能损失 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的 同步和调度开销而可用的资源不变。健壮性降低 编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。一个线程崩溃了系统发送信号是以进程为单位的所以整个进程都会崩溃。缺乏访问控制 进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响多线程共享地址空间。 Linux线程操作
使用Linux线程接口的时候我们需要导入线程库。例如 g -o main main.cpp -stdc11 -lpthread
这个pthread就是Linux自带的线程库但是我们上面线程概念提到了Linux没有真正的线程而是用进程模拟的线程(LWP)所以Linux也没有真正的线程接口Linux提供的是轻量级进程的系统接口然后对这个接口进行封装封装成线程库在用户的角度看这就是线程控制的接口从而完成对线程的控制。任何操作系统都会自带线程库
线程创建
线程创建使用pthread_create这个函数接口需要包含头文件pthread.h 参数解析
pthread_t *thread //线程idconst pthread_attr_t *attr //设置线程属性一般为nullptrvoid *(start_routine) (void ) //回调函数会执行传入的函数指针(返回值void,参数voidvoid *arg //可以作为函数参数
线程等待
在主线程调用这个函数会让主线程阻塞等待线程结束并不是所有的情况都必须调用这个函数。 传入对应的线程id并且阻塞等待。第二个参数是一个输出型参数用于接收线程返回值。
线程终止
线程终止可能由以下几种情况造成
一个线程正常情况下结束有可能是线程函数执行结束了return void*。还有一种就是调用这个函数线程调用这个函数让自己退出。 主线程调用这个函数就可以取消正在执行的线程。返回值为-1。
线程分离 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。 分离线程的接口只需要传入线程id就可以了。 线程分离之后是不可以join的join会返回报错码线程分离是一种属性状态如果主线程join等待某个线程会查看这个线程的状态是否是分离的。如果是分离状态就会直接报错。如果先join线程再分离线程会检测不到。
例如这样就会报错
void *run_thread(void *args){//pthread_detach(pthread_t pthread_self()); //可以自己分离自己int cnt 5;while(cnt){cout我是线程:cnt--endl;sleep(1);}
}
int main(){pthread_t t1;pthread_create(t1,nullptr,run_thread,nullptr);pthread_detach(t1);//可以再主线程里面分离int n pthread_join(t1,nullptr);if(n!0)//如果n!0表示阻塞等待失败打印返回的错误码{couterror:n:strerror(n)endl;}return 0;}线程间的私有和共享数据
线程共享进程数据但是也拥有自己的一部分数据 线程ID一组寄存器栈errno信号屏蔽字调度优先级 进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境: 文件描述符表每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录用户id和组id 理解线程库和线程id
我们学习的Linux里面其实是没有真正意义上的线程的Linux用进程模拟的线程。所谓的线程就是一个轻量级的进程所以Linux提供了对轻量级进程操作的接口而我们用的线程库就是对这些接口进行了封装。从用户的角度是对线程进行操作但是从OS的角度是对轻量级进程的操作。
这个线程库是一个用户级的动态库所以进程想要用这个库必须先加载到内存然后映射到进程地址空间的共享区中。
这个线程库里面可能会创建很多个线程所以需要对线程进行管理所以先描述再组织。每个线程都有一个结构体来对这些线程进行统一管理。
线程库中有很多个线程每个线程都是向数组一样排列每个线程都有一个起始地址这个起始地址就是线程id把这个线程id传给其他线程就可以获取该线程的属性信息。
每个线程都拥有自己的栈结构主线程用的是地址空间的栈。
深刻理解Linux多线程重点 当一个进程创建子进程时需要创建PCB、进程地址空间、页表等。是非常独立的。一个进程内可以有多个线程那么线程是怎么创建的呢 给一个进程创建线程时只会创建一个PCB这个PCB还是会指向这个进程进程地址空间内部有代码区每个线程指向代码区中不同的代码区域一个线程对应一个函数代码这样一个线程就是一个执行流。这就是为什么线程是进程内部的一个执行流。线程执行粒度更细因为可以执行进程中不同的代码控制粒度更细。调度成本更低因为PCB中存放进程地址空间的地址当CPU切换PCB时发现不用切换加载进程地址空间以及页表等一系列操作。但是最重要还是不用切换cachecache是一个集成在CPU里面的硬件也叫做高速缓存器当我们访问内存中的代码的时候会预先加载一部分代码到cache中减少IO提升效率这个也叫做局部性原理。CPU切换的线程如果是同一个进程cache不用切换数据使得切换成本更低。之前谈到进程是CPU的基本调度单位因为之前谈论的进程都是一个执行流一个进程只有一个PCB而多线程这里一个进程有多个PCB也就是多个执行流对于CPU来说其实调度切换的是进程还是线程CPU并不知道也并不重要CPU只需要可以通过PCB访问进程地址空间通过页表映射到内存就可以了。 但是并不是所有的操作系统都是这样设计多线程的这种是Linux下的多线程而windows的里面线程和进程是不同的Windows下的线程叫做TCB(线程控制块)而线程是进程的一个执行流必须遵守执行粒度更细调度成本更低所以Windows的设计是比较复杂的。Windows里面是有真线程的Linux下则是用进程的方案去模拟线程所以Linux没有真正意义上的线程都叫做轻量级进程(LWP)。Linux对比Windows复用代码结构更简单好维护效率更高。 一个进程里面有多个执行流有一个执行流是主执行流。每个进程都有一个pid一个进程里面有多个执行流每个执行流的pid当然都是一样的但是每个执行流都有一个LWP是不一样的CPU根据LWP来进行基本的调度。切换执行流如果pid不变证明还是一个进程不需要切换地址空间、cache等操作如果pid变了证明不是同一个进程了。
代码证明多线程有多个LWP有同一个pid
void *run_thread1(void *args){while(true){cout我是执行流1:*((int*)args)endl;sleep(1);}
}
void *run_thread2(void *args){while(true){cout我是执行流2:*((int*)args)endl;sleep(1);}
}
void *run_thread3(void *args){while(true){cout我是执行流3:*((int*)args)endl;sleep(1);}
}
int main(){pthread_t t1,t2,t3;int th11;pthread_create(t1,nullptr,run_thread1,th1);int th22;pthread_create(t2,nullptr,run_thread2,th2);int th33;pthread_create(t3,nullptr,run_thread3,th3);while(true){cout我是主执行流endl;sleep(1);}return 0;
}执行结果 这段代码使用了一下多线程证明一个线程有一个LWP。主执行流的LWP和进程的pid是相同的所以之前学习进程说的CPU根据pid调度进程也是对的。
这就是Linux下的多线程后续会更新互斥和同步的文章多多支持。