我一直试图找到一个好的定义,并理解线程到底是什么。

似乎我一定遗漏了一些明显的东西,但每次我读到什么是线程,它几乎是一个循环的定义,比如“线程是执行的线程”或“一种划分运行任务的方法”。嗯嗯。嗯?

It seems from what I have read that a thread is not really something concrete, like a process is. It is in fact just a concept. From what I understand of the way this works, a processor executes some commands for a program (which has been termed a thread of execution), then when it needs to switch to processing for some other program for a bit, it stores the state of the program it's currently executing for somewhere (Thread Local Storage) and then starts executing the other program's instructions. And back and forth. Such that, a thread is really just a concept for "one of the paths of execution" of a program that is currently running.

不像一个过程,它是一个资源的集合,等等。

作为一个定义的例子,它并没有真正帮助到我…

从维基百科:

“在计算机科学中,线程是执行线程的缩写。线程是程序将自身分割(称为“分裂”)为两个或多个同时(或伪同时)运行的任务的一种方法。不同操作系统的线程和进程各不相同,但一般来说,线程包含在进程中,同一进程中的不同线程共享相同的资源,而同一多任务操作系统中的不同进程则不相同。”

我说的对吗?错了吗?线程到底是什么?

编辑:显然,线程也有自己的调用堆栈,所以这是一个具体的东西。


当前回答

不幸的是,线程确实存在。线是有形的东西。就算你杀了一个,其他的人还是会逃。您可以生成新的线程....虽然每个线程不是它自己的进程,但它们在进程中单独运行。在多核机器上,两个线程可以同时运行。

http://en.wikipedia.org/wiki/Simultaneous_multithreading

http://www.intel.com/intelpress/samples/mcp_samplech01.pdf

其他回答

流程就像使用两台不同计算机的两个人,他们在必要时使用网络共享数据。线程就像使用同一台计算机的两个人,他们不必显式地共享数据,但必须小心地轮流使用。

从概念上讲,线程只是在同一个地址空间中忙碌的多个工蜂。每个线程都有自己的堆栈、程序计数器等等,但是一个进程中的所有线程都共享相同的内存。假设两个程序同时运行,但它们都可以访问相同的对象。

将此与流程进行对比。每个进程都有自己的地址空间,这意味着一个进程中的指针不能用于引用另一个进程中的对象(除非使用共享内存)。

我想需要理解的关键是:

进程和线程可以“同时运行”。 进程不共享内存(默认情况下),但是线程与同一进程中的其他线程共享它们的所有内存。 进程中的每个线程都有自己的堆栈和指令指针。

以下是来自雅虎的回答:

线程是一种编码构造 的体系结构不受影响 应用程序。单个过程 通常可能包含多个 线程。线程也可以直接 彼此沟通,因为他们 共享相同的变量。 进程是独立执行的 单位有自己的状态 信息。他们也用自己的 地址空间和只能交互 通过其他过程 进程间通信机制。

然而,简单地说,线程就像不同的“任务”。想象一下,当你在做某事的时候,比如你在一张纸上写下一个公式。这可以看作是一个线程。然后另一个线程是你在另一张纸上写别的东西。这就是多任务处理的原因。

据说英特尔处理器有“超线程”(AMD也有),它意味着能够更好地执行多个“线程”或多任务。

我不确定如何处理线程的后勤。我确实记得听说过处理器在它们之间来回切换,但我不是100%确定,希望其他人能回答这个问题。

正如进程代表虚拟计算机一样,线程 抽象表示一个虚拟处理器。

所以线程是一种抽象。

抽象降低了复杂性。因此,第一个问题是线程解决什么问题。第二个问题是如何实施。

关于第一个问题:线程使实现多任务更容易。这背后的主要思想是,如果每一项任务都可以分配给唯一的员工,那么多任务处理就没有必要了。实际上,就目前而言,可以进一步概括这个定义,并说线程抽象代表一个虚拟工作者。

Now, imagine you have a robot that you want to give multiple tasks. Unfortunately, it can only execute a single, step by step task description. Well, if you want to make it multitask, you can try creating one big task description by interleaving the separate tasks you already have. This is a good start but the issue is that the robot sits at a desk and puts items on it while working. In order to get things right, you cannot just interleave instructions but also have to save and restore the items on the table.

这是可行的,但现在仅通过查看您创建的大任务描述就很难将单独的任务分开。此外,保存和恢复表上的项的过程很乏味,并且使任务描述更加混乱。

这就是线程抽象的用武之地。它让你假设你有无数个机器人,每个机器人都坐在不同的房间里自己的办公桌前。现在,您可以将任务描述扔到一个容器中,其他一切都由线程抽象的实现者处理。还记得吗?如果有足够多的工人,没有人需要一心多用。

通常,表明你的视角是有用的,说robot是指真正的机器人,说virtual robot是指线程抽象提供给你的机器人。

至此,在任务完全独立的情况下,多任务处理问题得到了解决。然而,让机器人走出自己的房间,相互交流,朝着一个共同的目标努力,这不是很好吗?你可能已经猜到了,这需要协调。红绿灯,排队,应有尽有。

作为一种中间总结,线程抽象解决了多任务处理的问题,并为合作创造了机会。没有它,我们只有一个机器人,所以合作是不可想象的。但是,它也给我们带来了协调(同步)的问题。现在我们知道了胎面抽象解决了什么问题,作为奖励,我们也知道了它带来了什么新挑战。


但是等等,为什么我们首先关心多任务处理呢?

首先,如果任务涉及等待,多任务处理可以提高性能。例如,当洗衣机在运转时,你可以很容易地开始准备晚餐。当你的晚餐在上面的时候,你可以把衣服晾出来。请注意,在这里等待是因为一个独立的组件为您完成了这项工作。涉及等待的任务称为I/O绑定任务。

其次,如果多任务处理是快速完成的,从鸟瞰的角度来看,它就表现为并行性。这有点像人眼将一系列静止图像感知为快速连续显示的运动图像。如果我给Alice写了一秒给Bob也写了一秒,你能分辨出这两封信是同时写的还是交替写的吗,如果你只看我每两秒在做什么?搜索多任务操作系统以获得更多信息。


现在,让我们关注如何实现线程抽象的问题。

从本质上讲,实现线程抽象就是编写一个任务,一个主任务,它负责调度所有其他任务。

要问的一个基本问题是:如果调度器调度所有任务,并且调度器也是一个任务,那么谁调度调度器?

Let's brake this down. Say you write a scheduler, compile it and load it into the main memory of a computer at the address 1024, which happens to be the address that is loaded into the processor's instruction pointer when the computer is started. Now, your scheduler goes ahead and finds some tasks sitting precompiled in the main memory. For example, a task starts at the address 1,048,576. The scheduler wants to execute this task so it loads the task's address (1,048,576) into the instruction pointer. Huh, that was quite an ill considered move because now the scheduler has no way to regain control from the task it has just started.

一种解决方案是在执行之前在任务描述中插入到调度程序(地址1024)的跳转指令。实际上,你不应该忘记保存机器人正在工作的桌子上的物品,所以在跳跃之前你还必须保存处理器的寄存器。这里的问题是,很难判断在哪里插入跳转指令。如果它们太多,就会产生过多的开销;如果它们太少,就会有一个任务独占处理器。

A second approach is to ask the task authors to designate a few places where control can be transferred back to the scheduler. Note that the authors don't have to write the logic for saving the registers and inserting the jump instruction because it suffices that they mark the appropriate places and the scheduler takes care of the rest. This looks like a good idea because task authors probably know that, for example, their task will wait for a while after loading and starting a washing machine, so they let the scheduler take control there.

上述两种方法都无法解决错误或恶意任务的问题,例如,它陷入无限循环,无法跳转到调度器所在的地址。

现在,如果你不能用软件解决问题该怎么办?在硬件上解决!所需要的是一个连接到处理器上的可编程电路,就像一个闹钟一样。调度器设置一个定时器和它的地址(1024),当定时器用完时,告警保存寄存器并将指令指针设置为调度器所在的地址。这种方法称为抢占式调度。


现在您可能已经开始意识到实现线程抽象与实现链表不同。线程抽象最著名的实现者是操作系统。它们提供的线程有时称为内核级线程。由于操作系统无法承受失去控制的后果,所有主要的通用操作系统都使用抢占式调度。

Arguably, operating systems feel like the right place to implement the thread abstraction because they control all the hardware components and can suspend and resume threads very wisely. If a thread requests the contents of a file stored on a hard drive from the operating system, it immediately knows that this operation will most likely take a while and can let another task occupy the processor in the meanwhile. Then, it can pause the current task and resume the one that made the request, once the file's contents are available.

然而,故事并未就此结束,因为线程也可以在用户空间中实现。这些实现者通常是编译器。有趣的是,据我所知,内核级线程是线程所能得到的最强大的线程。那么,我们为什么要用用户级线程呢?原因当然是性能。用户级线程更轻量级,因此您可以创建更多的线程,通常暂停和恢复线程的开销很小。

用户级线程可以使用async/await实现。您还记得实现控制返回调度程序的一个选项是让任务作者指定可以发生转换的位置吗?async和await关键字正是用于此目的。


现在,如果你已经做到了这一步,请做好准备,因为真正的乐趣即将到来!

您是否注意到我们几乎没有讨论并行性?我的意思是,我们不是使用线程并行地运行相关的计算,从而提高吞吐量吗?嗯,不安静..实际上,如果你只想要并行,你根本不需要这个机制。您只需创建与您拥有的处理单元数量一样多的任务,并且没有任何任务必须暂停或恢复。你甚至不需要调度程序,因为你不需要一心多用。

在某种意义上,并行是一个实现细节。如果您仔细想想,线程抽象的实现者可以在底层使用尽可能多的处理器。你可以从1950年开始编译一些编写良好的多线程代码,在今天的多核上运行,然后看到它利用了所有的核。重要的是,编写这段代码的程序员可能没有预料到这段代码会在多核上运行。

您甚至可以认为,当线程被用于实现并行性时,它们被滥用了:即使人们知道他们不需要多任务这一核心功能,但他们还是使用线程来获得并行性。


最后需要注意的是,用户级线程本身不能提供并行性。还记得开头的那句话吗?操作系统在虚拟计算机(进程)中运行程序,默认情况下通常配备单个虚拟处理器(线程)。无论您在用户空间中使用什么魔法,如果您的虚拟计算机只有一个虚拟处理器,那么您就不能并行运行代码。

我们想要什么?当然,我们需要并行性。但是我们也需要轻量级线程。因此,许多线程抽象的实现者开始使用一种混合方法:他们启动与硬件中处理单元数量一样多的内核级线程,并在几个内核级线程之上运行许多用户级线程。本质上,并行由内核级处理,多任务由用户级线程处理。


Now, an interesting design decision is what threading interface a language exposes. Go, for example, provides a single interface that allows users to create hybrid threads, so called goroutines. There is no way to ask for, say, just a single kernel-level thread in Go. Other languages have separate interfaces for different kinds of threads. In Rust, kernel-level threads live in the standard library, while user-level and hybrid threads can be found in external libraries like async-std and tokio. In Python, the asyncio package provides user-level threads while multithreading and multiprocessing provide kernel-level threads. Interestingly, the threads multithreading provides cannot run in parallel. On the other hand, the threads multiprocessing provides can run in parallel but, as the library's name suggests, each kernel-level thread lives in a different process (virtual machine). This makes multiprocessing unsuitable for certain tasks because transferring data between different virtual machines is often slow.

更多资源:

操作系统:原理与实践,作者:Thomas and Anderson

并发不是罗伯派克的并行

并行性和并发性需要不同的工具

Rust中的异步编程

Rust的异步转换内部

Rust的Async/Await之旅

你的功能是什么颜色?

为什么goroutine而不是线程?

为什么我的程序在更多cpu时运行得更快?

John Reese -用AsyncIO和Multiprocessing跳出GIL思考- PyCon 2018

线程是处理器寄存器(对于单核)的一组独立值。因为这包括指令指针(又名程序计数器),它控制执行的顺序。它还包括堆栈指针,它最好为每个线程指向一个唯一的内存区域,否则它们会相互干扰。

线程是受控制流(函数调用、循环、goto)影响的软件单元,因为这些指令操作在指令指针上,而指令指针属于特定的线程。线程通常根据一些优先级方案进行调度(尽管可以设计一个系统,每个处理器核心有一个线程,在这种情况下,每个线程总是在运行,不需要调度)。

事实上,该指令指针的值和存储在该位置的指令足以确定该指令指针的新值。对于大多数指令来说,这只是按照指令的大小将IP向前推进,但是控制流指令以其他可预测的方式改变IP。IP值的序列形成了在程序代码中编织的执行路径,从而产生了“线程”的名称。

为了正式地定义线程,我们必须首先了解线程操作的边界。

当计算机程序从某个存储器载入计算机内存并开始执行时,它就成为一个进程。一个进程可以由一个处理器或一组处理器执行。内存中的进程描述包含重要信息,如程序计数器,它跟踪程序中的当前位置(即当前正在执行的指令),寄存器,变量存储,文件句柄,信号,等等。

线程是程序中这样的指令序列,可以独立于其他代码执行。如图所示为概念:

线程位于相同的进程地址空间中,因此,进程内存描述中的大部分信息可以跨线程共享。

有些信息不能复制,比如堆栈(每个线程指向不同内存区域的堆栈指针)、寄存器和特定于线程的数据。这些信息足以允许线程独立于程序的主线程和程序中的一个或多个其他线程进行调度。

运行多线程程序需要明确的操作系统支持。幸运的是,大多数现代操作系统都支持线程,如Linux(通过NPTL)、BSD变体、Mac OS X、Windows、Solaris、AIX、HP-UX等。操作系统可以使用不同的机制来实现多线程支持。

在这里,你可以找到关于这个主题的更多信息。这也是我的信息来源。

让我补充一句来自Edward Lee和Seshia的《嵌入式系统导论》:

Threads are imperative programs that run concurrently and share a memory space. They can access each others’ variables. Many practitioners in the field use the term “threads” more narrowly to refer to particular ways of constructing programs that share memory, [others] to broadly refer to any mechanism where imperative programs run concurrently and share memory. In this broad sense, threads exist in the form of interrupts on almost all microprocessors, even without any operating system at all (bare iron).