六、java-操作系统-6

1.21 请介绍线程之间的通信方式。

参考回答

  1. 锁机制:包括互斥锁、条件变量、读写锁互斥锁提供了以排他方式防止数据结构被并发修改的方法。读写锁允许多个线程同时读共享数据,而对写操作是互斥的。条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  3. 信号机制(Signal):类似进程间的信号处理线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

1.22 说一说进程的状态。

参考回答

  1. 进程的3种基本状态:运行、就绪和阻塞。

(1)就绪:当一个进程获得了除处理机以外的一切所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。

(2)运行:当一个进程在处理机上运行时,则称该进程处于运行状态。处于此状态的进程的数目小于等于处理器的数目,对于单处理机系统,处于运行状态的进程只有一个。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

(3)阻塞:也称为等待或睡眠状态,一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。

其转移图如下:

  1. 进程的五种状态

    创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态

    就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行

    执行状态:进程处于就绪状态被调度后,进程进入执行状态

    阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用

    终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行

1.23 CPU调度的最小单位是什么?线程需要CPU调度吗?

参考回答

  1. 进程是CPU分配资源的最小单位,线程是CPU调度的最小单位。

  2. 线程是比进程更小的能独立运行的基本单位,需要通过CPU调度来切换上下文,达到并发的目的。

1.24 进程之间共享内存的通信方式有什么好处?

参考回答

采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。

实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

1.25 如何杀死一个进程?

参考回答

  1. 杀死父进程并不会同时杀死子进程:每个进程都有一个父进程。可以使用 pstreeps 工具来观察这一点。

    # 启动两个虚拟进程
    $ sleep 100 &
    $ sleep 101 &
    
    $ pstree -p
    init(1)-+
            |-bash(29051)-+-pstree(29251)
                          |-sleep(28919)
                          `-sleep(28964)
    
    $ ps j -A
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        0     1     1     1 ?           -1 Ss       0   0:03 /sbin/init
    29051  1470  1470 29051 pts/2     2386 SN    1000   0:00 sleep 100
    29051  1538  1538 29051 pts/2     2386 SN    1000   0:00 sleep 101
    29051  2386  2386 29051 pts/2     2386 R+    1000   0:00 ps j -A
        1 29051 29051 29051 pts/2     2386 Ss    1000   0:00 -bash
    

    调用 ps 命令可以显示 PID(进程 ID) 和 PPID(父进程 ID)。

    杀死父进程后,子进程将会成为孤儿进程,而 init 进程将重新成为它的父进程。

  2. 杀死进程组或会话中的所有进程

    $ kill -SIGTERM -- -19701

    这里用一个负数 -19701 向进程组发送信号。如果传递的是一个正数,这个数将被视为进程 ID 用于终止进程。如果传递的是一个负数,它被视为 PGID,用于终止整个进程组。负数来自系统调用的直接定义。

    杀死会话中的所有进程与之完全不同。即使是具有会话 ID 的系统,例如 Linux,也没有提供系统调用来终止会话中的所有进程。需要遍历 /proc 输出的进程树,收集所有的 SID,然后一一终止进程。

    Pgrep 实现了遍历、收集并通过会话 ID 杀死进程的算法。可以使用以下命令:

    pkill -s <SID>

1.26 说一说kill的原理。

参考回答

  • kill 命令的执行原理是这样的,kill 命令会向操作系统内核发送一个信号(多是终止信号)和目标进程的 PID,然后系统内核根据收到的信号类型,对指定进程进行相应的操作。kill 命令的基本格式如下:

    [root@localhost ~]# kill [信号] PID

  • kill 命令是按照 PID 来确定进程的,所以 kill 命令只能识别 PID,而不能识别进程名。

  • kill 命令只是“发送”一个信号,因此,只有当信号被程序成功“捕获”,系统才会执行 kill 命令指定的操作;反之,如果信号被“封锁”或者“忽略”,则 kill 命令将会失效。

1.27 介绍下你知道的锁。

参考回答

  1. 悲观锁

悲观锁并不是某一个锁,是一个锁类型,无论是否并发竞争资源,都会锁住资源,并等待资源释放下一个线程才能获取到锁。 这明显很悲观,所以就叫悲观锁。这明显可以归纳为一种策略,只要符合这种策略的锁的具体实现,都是悲观锁的范畴。

  1. 乐观锁

与悲观锁相对的,乐观锁也是一个锁类型。当线程开始竞争资源时,不是立马给资源上锁,而是进行一些前后值比对,以此来操作资源。例如常见的CAS操作,就是典型的乐观锁。示例如下

int cas(long *addr, long old, long new) {
    /* 原子执行 */
    if(*addr != old)
        return 0;
    *addr = new;
    return 1;
}
  1. 自旋锁

自旋锁是一种基础的同步原语,用于保障对共享数据的互斥访问。与互斥锁的相比,在获取锁失败的时候不会使得线程阻塞而是一直自旋尝试获取锁。当线程等待自旋锁的时候,CPU不能做其他事情,而是一直处于轮询忙等的状态。

自旋锁主要适用于被持有时间短,线程不希望在重新调度上花过多时间的情况。实际上许多其他类型的锁在底层使用了自旋锁实现,例如多数互斥锁在试图获取锁的时候会先自旋一小段时间,然后才会休眠。如果在持锁时间很长的场景下使用自旋锁,则会导致CPU在这个线程的时间片用尽之前一直消耗在无意义的忙等上,造成计算资源的浪费。

// 用户空间用 atomic_flag 实现自旋互斥
#include <thread>
#include <vector>
#include <iostream>
#include <atomic>

std::atomic_flag lock = ATOMIC_FLAG_INIT;

void f(int n)
{
    for (int cnt = 0; cnt < 100; ++cnt) {
        while (lock.test_and_set(std::memory_order_acquire))  // 获得锁
             ; // 自旋
        std::cout << "Output from thread " << n << '\n';
        lock.clear(std::memory_order_release);               // 释放锁
    }
}

int main()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 10; ++n) {
        v.emplace_back(f, n);
    }
    for (auto& t : v) {
        t.join();
    }
}
  1. 公平锁

多个线程竞争同一把锁,如果依照先来先得的原则,那么就是一把公平锁。

  1. 非公平锁

多个线程竞争锁资源,抢占锁的所有权。

  1. 共享锁

多个线程可以共享这个锁的拥有权。一般用于数据的读操作,防止数据被写修改。共享锁的代码示例如下:

    #include <shared_mutex>
    #include <mutex>
    #include <iostream>
    #include <thread>
    #include <chrono>

    std::shared_mutex test_lock;

    std::mutex cout_lock;

    int arr[3] = {11, 22, 33};

    void unique_lock_demo(int id)
    {
        std::unique_lock lock{test_lock};

        for(int i =0; i < 3; i++)
        {
                arr[i] = i + 100 * id;
        }

        for(int i = 0; i < 3; i++)
        {
            std::unique_lock pl(cout_lock);
            std::cout << "In unique: " << id << ": " << arr[i] << std::endl;
            pl.unlock();
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }


    void shared_lock_demo(int id)
    {
        std::shared_lock lock{test_lock};

        for(int i = 0; i < 3; i++)
        {
            std::unique_lock pl(cout_lock);
            std::cout << "In shared " << id << ": " << arr[i] << std::endl;
            pl.unlock();
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    }

    int main()
    {

       std::thread t3(unique_lock_demo,3);
       std::thread t4(unique_lock_demo,4);
       std::thread t1(shared_lock_demo,1);
       std::thread t2(shared_lock_demo,2);

       t1.join();
       t2.join();
       t3.join();
       t4.join();
       return 0;
    }

输出为:

    In unique: 3: 300
    In unique: 3: 301
    In unique: 3: 302
    In shared 1: 300
    In shared 2: 300
    In shared 1: 301
    In shared 2: 301
    In shared 1: 302
    In shared 2: 302
    In unique: 4: 400
    In unique: 4: 401
    In unique: 4: 402

从这个输出可以看出:

  • 如果一个线程已经获取了共享锁,则其他任何线程都无法获取互斥锁,但是可以获取共享锁
  • 从这个输出可以看出,验证了如果一个线程已经获取了互斥锁,则其他线程都无法获取该锁。
  1. 死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

mutex;   //代表一个全局互斥对象
void  A()
{
    mutex.lock();
    //这里操作共享数据
    B();  //这里调用B方法
    mutex.unlock();
    return;
}
void  B()
{
    mutex.lock();
    //这里操作共享数据
    mutex.unlock();
    return;
}
全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务