Linux进程、线程、协程,Java/Golang多线程并发

5 篇文章 2 订阅
2 篇文章 0 订阅

目录

Linux

进程

线程

线程状态

协程

相关命令 

ps

kill

pkill

JAVA多线程并发

线程的新建

通过实现 Runnable 接口来创建线程

通过继承Thread来创建线程

通过 Callable 和 Future 创建线程

Golang多线程并发

并发和并行

协程和线程

goroutine 只是由官方实现的超级"线程池"

通道(channel)

通道缓冲区

Go 遍历通道与关闭通道


Linux

进程

保存在硬盘上的程序运行之后,会在内存中形成一个独立的内存体,它有自己独立的地址空间,有自己的堆。操作系统会以进程为单位来分配系统资源(CPU的时间片,内存资源等)。进程是资源分配的最小单位。


线程

又称轻量级进程,是操作系统调度CPU执行的最小单位。

线程状态

新建状态(NEW):新创建的一个线程对象。

就绪状态(RUNNABLE):线程对象被创建并被调用后,该线程会进入可运行线程池中,等待调度,获取CPU的使用权。

运行状态(RUNNING):就绪状态的线程获得了CPU的时间片(使用权),即进入运行状态。

阻塞状态(BLOCKED):是指运行状态的线程放弃了CPU的使用权,停止运行,并转入阻塞状态。

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

死亡状态(DEAD):线程执行结束,该线程的生命周期结束。

注:线程休眠属于阻塞状态,当线程结束休眠后将会重新进入就绪状态(RUNNABLE),并重新等待调用。


协程

个人理解:在CPU时间片轮转的基础之上的一种时间复用技术。  


相关命令 

ps

常用参数:

a显示现行终端机下的所有程序,包括其他用户的程序
-A显示所有程序
c显示每个程序真正的指令名称,而不包含路径
-C <指令名称>指定执行指令的名称,并列出该指令的程序的状况
-d显示所有程序,但不包括阶段作业管理员的程序
e列出程序时,显示每个程序所使用的环境变量
-f显示UID,PPIP,C与STIME栏位
f用ASCII字符显示树状结构,表达程序间的相互关系
g显示现行终端机下的所有程序,包括所属组的程序
-G <群组识别码>列出属于该群组的程序的状况
h不显示标题列
-H显示树状结构,表示程序间的相互关系
-j采用工作控制的格式显示程序状况
-l采用详细的格式来显示程序状况
L列出栏位的相关信息
-m显示所有的执行绪
n以数字来表示USER和WCHAN栏位
-N显示所有的程序,除了执行ps指令终端机下的程序之外
-p <程序识别码>指定程序识别码,并列出该程序的状况
r只列出现行终端机正在执行中的程序
-s <阶段作业>列出隶属该阶段作业的程序的状况
s采用程序信号的格式显示程序状况
S列出程序时,包括已中断的子程序资料
-t <终端机编号>列出属于该终端机的程序的状况
-T显示现行终端机下的所有程序
u以用户为主的格式来显示程序状况
-U <用户识别码>列出属于该用户的程序的状况
U <用户名称>列出属于该用户的程序的状况
v采用虚拟内存的格式显示程序状况
-V或V显示版本信息
-w或w采用宽阔的格式来显示程序状况
x显示所有程序,不以终端机来区分
X采用旧式的Linux i386登陆格式显示程序状况
-y配合选项”-l”使用时,不显示F(flag)栏位,并以RSS栏位取代ADDR栏位

参考实例:

显示系统中全部的进程信息,含详细信息:

[root@vanqiyeah ~]# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.1 125716  1896 ?        Ss   09:15   0:00 /usr/lib/systemd/systemd --switched-root --system --d
root          2  0.0  0.0      0     0 ?        S    09:15   0:00 [kthreadd]
root          4  0.0  0.0      0     0 ?        S<   09:15   0:00 [kworker/0:0H]
root          6  0.0  0.0      0     0 ?        S    09:15   0:00 [ksoftirqd/0]
...以下省略...

 结合输出重定向,将当前进程信息保留备份至指定文件:

[root@vanqiyeah /]# ps aux > ./1.txt
[root@vanqiyeah /]# cat 1.txt
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.2 125716  2004 ?        Ss   09:15   0:00 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root          2  0.0  0.0      0     0 ?        S    09:15   0:00 [kthreadd]
root          4  0.0  0.0      0     0 ?        S<   09:15   0:00 [kworker/0:0H]
root          6  0.0  0.0      0     0 ?        S    09:15   0:00 [ksoftirqd/0]
...以下省略...

结合管道操作符,将当前系统运行状态中指定进程信息过滤出来: 

[root@vanqiyeah /]# ps -aux|grep docker

kill

kill的误区:kill 命令是向进程发送信号,kill 不是杀死的意思,-9 表示无条件退出,但由进程自行决定是否退出,这就是为什么 kill -9 终止不了系统进程和守护进程的原因。

常用参数:

-l列出系统支持的信号
-s指定向进程发送的信号
-a不限制命令名和进程号的对应关系
-p不发送任何信号
1(HUP)重新加载进程
9(KILL)杀死(结束)一个进程
15(TERM)正常停止一个进程

 参考实例:

列出系统支持的全部信号列表:

[root@vanqiyeah /]# kill -l

结束某个指定的进程(数字为对应的PID值):

[root@vanqiyeah /]# kill 3809

强制结束某个指定的进程(数字为对应的PID值):

[root@vanqiyeah /]# kill -9 3809

pkill

常用参数:

-o仅向找到的最小(起始)进程号发送信号
-n仅向找到的最大(结束)进程号发送信号
-P指定父进程号发送信号
-g指定进程组
-t指定开启进程的终端

参考实例:

仅向找到的最小(起始)进程号发送信号:

[root@vanqiyeah /]# pkill -o

仅向找到的最大(结束)进程号发送信号:

[root@vanqiyeah /]# pkill -n

JAVA多线程并发

线程的新建

  • 继承Thread类,重写run方法,调用start方法开启线程内部调用run方法
  • 实现Runable接口,重写run方法
  • callable接口,重写run方法,futureTask方法获取线程返回值

通过实现 Runnable 接口来创建线程

为了实现Runnable,一个类只需要执行一个方法调用run(),申明如下:

public void run()

在创建一个实现 Runnable 接口的类之后,可以在类中实例化一个线程对象。Thread 定义了几个构造方法,下面的这个是经常使用的:

Thread(Runnable threadOb,String threadName);

threadOb 是一个实现 Runnable 接口的类的实例。新线程创建之后,你调用它的 start() 方法它才会运行。

void start();

实例如下:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }   
}

运行结果如下:

通过继承Thread来创建线程

另一种方法是创建一个新的类,该类继承Thread类,然后创建一个该类的实例。继承类必须重写run()方法,也必须调用start()才能执行。

实例如下:

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
class TestThread {

    public static void main(String args[]) {
        ThreadDemo T1 = new ThreadDemo( "Thread-1");
        T1.start();

        ThreadDemo T2 = new ThreadDemo( "Thread-2");
        T2.start();
    }
}

运行结果:

通过 Callable 和 Future 创建线程

创建Callable接口的实现类,并实现Call()方法,该call()方法将作为线程执行体。

实例如下:

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args) {
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " i" + i);
            if (i == 20) {
                new Thread(ft, "return:").start();
            }
        }
        try {
            System.out.println("return:" + ft.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }

    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }
}

运行结果:


Golang多线程并发

并发和并行

   A. 多线程程序在一个核的cpu上运行,就是并发。
   B. 多线程程序在多个核的cpu上运行,就是并行。


协程和线程

 协程:独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的。
 线程:一个线程上可以跑多个协程,协程是轻量级的线程。


goroutine 只是由官方实现的超级"线程池"

Go 语言支持并发,只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。goroutine 语法格式:

go 函数名( 参数列表 )

例如:

go f(x, y, z)

开启一个新的 goroutine:

f(x, y, z)

实例如下:

package main

import (
        "fmt"
        "time"
)

func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("world")
        say("hello")
}

运行结果:


通道(channel)

通道(channel)是用来传递数据的一个数据结构。通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v

声明通道使用chan关键字即可,通道在使用前必须先被创建:

ch := make(chan int)

实例如下:

package main

import "fmt"

func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}

func main() {
        s := []int{7, 2, 8, -9, 4, 0}

        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收

        fmt.Println(x, y, x+y)
}

运行结果:


通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

实例如下:

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}

运行效果:


Go 遍历通道与关闭通道

通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

实例如下:

package main

import (
        "fmt"
)

func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)
}

func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

运行结果:

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会调制解调的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值