吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1952|回复: 7
收起左侧

[其他转载] 操作系统 --多线程编程知识详解

[复制链接]
黑白客 发表于 2021-4-20 11:09

操作系统 --多线程编程
概述
多线程模型
线程库
线程问题
操作系统实例

操作系统 --多线程编程

介绍线程的概念——构成多线程计算机系统基础的CPU使用的基本单位

讨论Pthreads、Win32和Java线程库的api

研究与多线程编程相关的问题

概述

单线程和多线程

单线程在程序中只有一个registers(寄存器),一个堆栈(stack)来执行一个线程

而多线程有多个registers 和stack 一组来执行多个thread

多线程服务模型

客户端 想服务端发送一个请求

客户端创建一个新线程来处理这个请求

然后将结果返回给客户端

优点

一个交互式应用程序可能允许一个程序继续运行,即使它的一部分被阻塞或正在执行一个长时间的操作,

从而增加对用户的响应。

例如,一个多线程的Web浏览器可以允许用户在一个线程中交互,而在另一个线程中加载图像。

资源共享:进程只能通过共享内存或由程序员安排的消息传递来共享资源。

线程共享它们默认所属进程的内存和资源。

共享代码和数据的好处在于,它允许应用程序在相同的地址空间内拥有多个不同的活动线程。

可伸缩性:在多处理器架构中,多线程的好处可以大大增加,因为线程可以在不同的处理器上并行运行。

多cpu机器上的多线程增加了并行性。

多核编程

多核系统给程序员带来压力,挑战包括

分裂活动

平衡

数据分割

数据依赖

测试和调试

在单核上的并发执行

那么所有的线程只需要排成一列等待执行即可

在多核上的并发执行

会有多个列之间并行执行

多线程模型

对线程的支持可以在用户级提供给用户线程,也可以由内核提供给内核线程。

在内核之上支持用户线程,在没有内核支持的情况下管理用户线程。

内核线程由操作系统直接支持和管理。

几乎所有的当代操作系统,包括Windows XP/2000、Solaris、Linux、Mac OS X和Tru64 UNIX(以前的数字UNIX),都支持内核线程。

用户线程和内核线程之间必须存在关系。

建立这种关系的三种常见方式:

多对一

许多用户级线程映射到单个内核线程。线程管理由用户空间中的线程库完成,效率高。

但是,如果一个线程进行了一个阻塞的系统调用,整个进程将会阻塞。

同时只有一个线程可以访问内核,多个线程无法在多处理器上并行运行。

例子:

Solaris绿色线程

GNU轻便线程

一对一的

每个用户级线程都映射到一个内核线程。

当一个线程进行阻塞的系统调用时,允许另一个线程运行。

也允许多个线程在多处理器上并行运行。

创建用户线程需要创建相应的内核线程限制系统支持的线程数量

例子

Windows NT / 2000 / XP

Linux

Solaris 9及以后版本

多对多

将许多用户级线程多路复用到数量较少或相等的内核线程

允许开发人员按照自己的意愿创建多个用户线程,因为线程在同一时间只能调度一个内核,所以不能获得真正的并发性。

但是内核线程可以在多处理器上并行运行。

也允许另一个线程在一个线程进行阻塞的系统调用时运行。

版本9之前的Solaris

Windows NT/2000与ThreadFiber包

多对多模型(称为两级模型)的一个流行变体是,它还允许将用户线程绑定到内核线程

例子

IRIX

hp - ux

Tru64 UNIX

Solaris 8和更早的版本

线程库

线程库为程序员提供了创建和管理线程的API。

两种主要的实现方式

提供一个完全在用户空间中,没有内核支持的库。库的所有代码和数据结构都存在于用户空间中。调用库中的函数会导致用户空间中的本地函数调用,而不是系统调用。

操作系统直接支持的内核级库。该库的代码和数据结构存在于内核空间中。调用库的API中的函数会导致对内核的系统调用。

现在有三个主线程库在使用

POSIX Pthreads

Win32

Java

Pthreads可以作为用户级库,也可以作为内核级库

Win32线程库是一个内核级库

Java thread API允许在Java程序中直接创建和管理线程。

然而,由于JVM运行在主机操作系统之上,Java线程API通常使用主机系统上可用的线程库来实现。

让我们描述一下使用这三个线程库创建基本线程的情况。

设计一个多线程程序,使用众所周知的求和函数在单独的线程中执行非负整数的求和

N=3, sum = 0+1+2+3 = 6

N = 5, sum = 0+1+2+3+4+5 = 15

地址

可以作为用户级或内核级提供

用于线程创建和同步的POSIX标准(IEEE 1003.1c) API

API指定线程库的行为,实现取决于库的开发

适用于UNIX操作系统(Solaris、Linux、Mac OS X)

使用Pthreads API的多线程C程序

win32 threads

使用Win32线程库创建线程的技术类似于Pthreads技术。

由不同线程共享的数据(sum)是全局声明的。

在单独的线程中执行的sum()函数。

线程是使用CreateThread()函数创建的。一组属性被传递给这个函数

使用WaitForSingleObject()函数,它会导致创建线程阻塞,直到求和线程存在为止。

  • 使用Win32 API的多线程C程序

java 线程

Java线程由JVM管理

通常使用底层操作系统提供的线程模型实现

可以创建Java线程:

创建从Thread类派生的新类并重写其run()方法,或

定义一个实现Runnable接口(更常用)的类。

当一个类实现Runnable时,它必须定义一个run()方法。

实现run()方法的代码作为单独的线程运行。

一个非负整数求和的Java程序

class Sum implements Runnable{

​ private int upper;

​ public Sum ( int upper){

​     this.upper = upper;

}

public void run() {

​ int sum = 0;

for (int i= 0 , i <= upper; i++){

​ sum += i;

}

}

}

线程问题

关于多线程程序需要考虑的一些问题。

fork()和exec()系统调用的语义

目标线程的线程取消

异步或递延

信号处理

线程池

表数据

调度程序激活

fork()和exec()的语义

第3章描述了如何使用fork()系统调用来创建一个独立的、重复的进程。

fork()和exec()系统调用的语义在多线程程序中会发生变化

如果程序中的一个线程调用fork(),新进程是否复制所有线程,或者新进程是单线程的?

有些UNIX系统有两个版本的fork(),一个复制所有线程,另一个只复制调用fork()系统调用的线程。

如果一个线程调用exec()系统调用,在exec()参数中指定的程序将替换整个进程——包括所有线程。

fork()的两个版本中使用哪个取决于应用程序。

如果fork之后立即调用exec(),则没有必要复制所有线程,因为在exec()的参数中指定的程序将取代进程。在这种情况下,只复制调用线程是合适的。

但是,如果单独的进程在fork之后没有调用exec(),那么单独的进程应该复制所有线程。

线程取消

在一个线程完成之前终止它

两个的一般方法:

异步取消立即终止目标线程

延迟取消允许目标线程定期检查是否应该取消它

信号处理

在UNIX系统中,信号用于通知进程发生了特定的事件

信号处理器是用来处理信号的

信号是由特定的事件产生的

信号被传递给进程

信号一旦传递,就必须被处理

选项:

将信号传递给应用该信号的线程

将信号传递给进程中的每个线程

将信号传递给进程中的某些线程

指定一个特定的线程来接收进程的所有信号

线程池

在等待工作的池中创建多个线程

优点:

通常使用现有线程服务请求比创建新线程稍快一些

允许将应用程序中的线程数与池的大小绑定

线程特定数据

属于一个进程的线程共享该进程的数据。

但是,允许每个线程拥有自己的数据副本(线程特定的数据)是很有用的

例如,在一个事务处理系统中,我们可以在一个单独的线程中为每个事务服务。每个事务可能被分配一个唯一的ID。

要将每个线程与其唯一的ID关联起来,我们可以使用特定于线程的数据。

大多数线程库都为特定于线程的数据提供某种形式的支持。

调度程序激活

M:M和两级模型都需要内核和线程库之间进行通信,动态调整适当的内核线程数,以确保最佳性能。

轻量级进程(LWP)——一个介于使用线程和内核线程之间的中间数据结构。

对于用户线程库,LWP似乎是一个虚拟处理器,应用程序可以在其上调度用户线程运行。

每个LWP都附加到一个内核线程

如果内核线程阻塞LWP阻塞用户线程阻塞。

调度程序激活

一个应用程序可能需要任意数量的LWPs来有效地运行。

在单个处理器上运行的cpu绑定应用程序。

由于一次只能运行一个线程,所以一个LWP就足够了。

一个I/ o密集型应用程序可能需要多个LWPs来执行。

每个并发阻塞系统调用都需要一个LWP。

例如,五个不同的文件读取请求同时发生,然后需要五个LWPs,因为所有的LWPs都可能在内核中等待I/O完成。

调度程序激活:用户线程库和内核之间通信的一种方案

内核为应用程序提供一组虚拟处理器(LWPs),应用程序可以将用户线程调度到可用的虚拟处理器上。

内核必须通知应用程序某些事件- upcall

upcall由带有upcall处理程序的线程库处理,并且upcall处理程序必须运行在虚拟处理器上。

这种通信允许应用程序维护正确的内核线程数量

操作系统实例

Windows XP的线程

实现一对一的映射,

通过使用线程库,任何属于进程的线程都可以访问进程的地址空间。

每个线程都包含

一个线程id

表示处理器状态的寄存器集

分开的用户和内核堆栈

专用数据存储区

寄存器集、堆栈和私有存储区域称为线程的上下文

线程的主要数据结构包括:

thread(执行线程块)

KTHREAD(内核线程块)

TEB(线程环境块)

Linux线程

实现一对一的映射,

通过使用线程库,任何属于进程的线程都可以访问进程的地址空间。

每个线程都包含

一个线程id

表示处理器状态的寄存器集

分开的用户和内核堆栈

专用数据存储区

寄存器集、堆栈和私有存储区域被称为threadLinux的上下文。linux为fork()系统调用提供了复制进程的传统功能。

Linux还提供了使用clone()系统调用创建线程的能力

然而,Linux不区分进程和线程。

Linux将它们称为任务,而不是进程或线程

当clone()被调用时,它会被传递一组标志,这些标志决定父任务和子任务之间要进行多少共享。

线程的主要数据结构包括:

thread(执行线程块)

KTHREAD(内核线程块)

TEB(线程环境块)

例如,如果向clone()传递标志CLONE_FS、CLONE_VM、CLONE_SIGHAND和CLONE_FILES,它们将共享相同的文件系统信息、相同的内存空间、相同的信号处理程序和相同的打开文件集。

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
萧然独奏 + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

jiangxu2012 发表于 2021-4-20 12:16
你这个排版真不错
woshitc008 发表于 2021-4-20 12:22
光去看你的 头像了 写了啥 还没看 ,确实不错 很大
BaconOle 发表于 2021-4-20 12:34
gentlespider 发表于 2021-4-20 13:09
我去,这头像好大
恶魔天尊 发表于 2021-4-20 13:30
头像比正文好看。。。
xiaolong620 发表于 2021-4-20 13:50
过来看头像的
wycdd 发表于 2022-6-18 22:17
我怎看不到头像?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-5 22:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表