布景常识
- 软件是怎么驱动硬件的?
硬件是需要相关的驱动程序才能履行,而驱动程序是安装在操作体系内核中。如果写了一个程序A,A程序想操作硬件工作,首要需要进行体系调用,由内核去找对应的驱动程序唆使硬件工作。而驱动程序怎么让硬件工作的呢?驱动程序作为硬件和操作体系之间的媒介,能够把操作体系中相关的指令翻译成硬件能够识别的电信号,同时,驱动程序也能够将硬件的电信号转为操作体系能够识别的指令。 - 进程、轻量级进程、线程联系
一个进程因为所运转的空间不同,被分为内核线程和用户进程,之所有称之为内核线程,是因为其不拥有虚拟地址空间。如果创立一个新的用户进程,会分配一个新的虚拟地址空间,不同用户进程之间资源是阻隔的。因为创立一个新的进程需要消耗许多的资源,而且在进程之间切换的价值也很贵重,因此引入了轻量级进程。轻量级进行本质上也是对内核线程的高层抽象,虽然不同的轻量级进程之间能够共享某些资源,但因为轻量级进程本质上仍是内核线程,如果进行轻量级线程之间的切换,需要进行体系调用,价值也是比较贵重的。内核本质上只能感知到进程的存在,像不同言语的多线程技能,是在用户进程的基础上创立的线程库,线程自身不参加处理器竞赛,而是由其所属的用户进程参加处理器的竞赛。 - 怎么了解用户态和内核态
首要咱们需要了解到计算机资源是有限的,不管是CPU资源、内存资源、IO资源、网络资源,为了确保这些资源的合理运用,需要有一个管控机制,而这个管控机制都是交于操作体系来处理的。用户态和内核态是操作体系的一种逻辑划分,本质上是进行权限操控,处于用户态的进程能够直接运用分配给其的内存空间,但如果想运用CPU等稀缺资源,处于用户态的进程就没有这个权限了,有必要通过体系调用,让当时进程进入内核态,这样能够有更大的权限去申请CPU资源、内存资源、IO资源等;
操作体系线程模型
java言语
线程模型
在Java诞生之初,在Java中就引入了线程,开始称之为“绿色线程”,完全由JVM进行管理,这和操作体系用户线程是多对一的完成,但随着操作体系对线程支撑越来越强壮,java中的线程完成选用了一对一的完成,即一个java线程对应于一个操作体系用户线程,可是这个线程的堆栈巨细是固定的,随着线程数量创立过多,或许导致内存溢出。在java19版本中引入了虚拟线程的概念,虚拟线程有一个动态的堆栈,能够增大和缩小,这和操作体系用户线程之间是一个多对多的联系,随着后面的发展,java中的线程模型会变得越来越强壮。
优缺陷
作为一对一的线程模型保护起来比较简单,可是因为每一个线程栈信息是固定的,不利于创立很多的线程,而且多线程操作时或许触及频频的体系调用,上下文切换价值高。
运用方法(以出产者顾客模型来阐明)
public class ThreadTest {
public static final Object P = new Object();
static List<Integer> list = new ArrayList<>();
@Test
public void test() throws Exception {
Thread thread1 = new Thread(()-> {
while(true) {
try {
product();
}catch (Exception e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
while(true) {
try {
consume();
}catch (Exception e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
private static void product() throws Exception {
synchronized (P) {
if(list.size() == 1) {
// 让出锁
P.wait();
}
list.add(1);
System.out.println("produce");
P.notify();
}
}
private static void consume() throws Exception {
synchronized (P) {
if(list.size() == 0) {
P.wait();
}
list.remove(list.size() - 1);
System.out.println("consume");
P.notify();
}
}
}
go言语
go言语线程模型
在go言语中,线程模型便是比较强壮了,包含了三个概念:内核线程(M)、goroutine(G)、G的上下文环境(P)。其中G表示基于协程创立的用户线程,M直接相关一个内核线程,P里面一般存放正在运转的goroutine的上下文环境(函数指针、堆栈地址和地址边界等)。
优缺陷
go言语中的线程模型算是很强壮了,引用了协程,线程栈巨细能够动态调整,很好地避免了java中目前的线程模型缺陷。
运用方法(以出产者顾客模型来阐明)
package main
import (
"fmt"
)
type ThreadTest struct {
lock chan int
}
func (t *ThreadTest) produce() {
for {
t.lock <- 10
fmt.Println("produce:", 10)
}
}
func (t *ThreadTest) consume() {
for {
v := <-t.lock
fmt.Println("consume:", v)
}
}
func main() {
maxLen := 10
t := &ThreadTest{
make(chan int, maxLen),
}
// 重点在这里,敞开新的协程,配合通道,让go的多线程变成十分高雅
go t.consume()
go t.produce()
select {}
}
c++言语
c++言语线程模型
在c++11中增加了操作thread库,提供对线程操作的进一步封装,而这个库底层是运用了pthread库,这个库底层选用了1:1线程模型,跟java中的线程模型相似。
优缺陷
作为一对一的线程模型保护起来比较简单,可是因为每一个线程栈信息是固定的,不利于创立很多的线程,而且多线程操作时或许触及频频的体系调用,上下文切换价值高。
运用方法(以出产者顾客模型来阐明)
#include
#include
#include
#include
static const int SIZE = 10;
static const int ITEM_SIZE = 30;
std::mutex mtx;
std::condition_variable not_full;
std::condition_variable not_empty;
int items[SIZE];
static std::size_t r_idx = 0;
static std::size_t w_idx = 0;
void produce(int i) {
std::unique_lock lck(mtx);
while((w_idx+ 1) % SIZE == r_idx) {
std::cout << "队列满了" << std::endl;
not_full.wait(lck);
}
items[w_idx] = i;
w_idx = (w_idx+ 1) % SIZE;
not_empty.notify_all();
lck.unlock();
}
int consume() {
int data;
std::unique_lock lck(mtx);
while(w_idx == r_idx) {
std::cout << "队列为空" << std::endl;
not_empty.wait(lck);
}
data = items[r_idx];
r_idx = (r_idx + 1) % SIZE;
not_full.notify_all();
lck.unlock();
return data;
}
void p_t() {
for(int i = 0; i < ITEM_SIZE; i++) {
produce(i);
}
}
void c_t() {
static int cnt = 0;
while(1) {
int item = consume();
std::cout << "消费第" << item << "个商品" << std::endl;
if(++cnt == ITEM_SIZE) {
break;
}
}
}
int main() {
std::thread producer(p_t);
std::thread consumer(c_t);
producer.join();
consumer.join();
}
python言语
python线程模型
python中的线程运用了操作体系的原生线程,python虚拟机运用了一个全局互斥锁(GIL)来互斥线程对Python虚拟机的运用,当一个线程获取GIL的权限之后,其他的线程有必要等候这个线程开释GIL锁,索引再多核CPU上,python多线程也会退化为单线程,无法运用多核的优势。
优缺陷
python言语多线程因为GIL的存在,在计算密集型场景上,很难体现到优势,而且因为触及线程切换的代码,反而或许性能还不如单线程好。
运用方法(以出产者顾客模型来阐明)
#! /usr/bin/python3
import threading
import random
import time
total = 100
lock = threading.Lock()
totalTime = 10
gTime = 0
class Consumer(threading.Thread):
def run(self):
global total
global gTime
while True:
cur = random.randint(10, 100)
lock.acquire()
if total >= cur:
total -= cur
print("{}运用了{}, 当时剩下{}".format(threading.current_thread(), cur, total))
else:
print("{}预备运用{},当时剩下{},缺乏,不能消费".format(threading.current_thread(), cur, total))
if gTime == totalTime:
lock.release()
break
lock.release()
time.sleep(0.7)
class Producer(threading.Thread):
def run(self):
global total
global gTime
while True:
cur = random.randint(10, 100)
lock.acquire()
if gTime == totalTime:
lock.release()
break
total += cur
print("{}出产了{}, 剩下{}".format(threading.current_thread(), cur, total))
gTime+= 1
lock.release()
time.sleep(0.5)
if __name__ == '__main__':
t1 = Producer(name="出产者")
t1.start()
t2 = Consumer(name="顾客")
t2.start()
总结
在目前的线程模型中,有1:1、M:1、M:N多种线程模型,具体选用哪种线程模型也和硬件和操作体系的支撑程度有关,像诞生比较早的言语,普通选用M:1、1:1线程模型,像c++、java。而新诞生不久的go言语,选用的是M:N线程模型,在多线程的支撑上更加强壮。
感觉了解一下线程模型仍是很有必要的,如果不清楚言语层面上的线程在操作体系层面怎么映射运用,在运用过程中就会不清不楚,或许会踩一些坑,咱们都知道在java中不同无限的创立线程,这会导致内存溢出,go言语中对多线程支撑更加强壮,许多事情不需要咱们再去重视了,在言语底层现已帮助咱们做了。
每种言语的底层细节太多了,如果想深入研究某一个技能,仍是得花精力去研究。
作者:京东零售 姜昌伟
来历:京东云开发者社区