详细了解多线程和多进程 C/C++

0、线程和进程

进程是指一个程序的运行实例,而线程是指进程中独立的执行流程

一个进程可以有多个线程,多个线程之间可以并发执行。

不同进程拥有不同的地址空间,互不相关,而不同线程共同拥有相同进程的地址空间。

创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多

特性 进程(Process) 线程(Thread)
资源分配 独立地址空间,拥有自己的资源 共享进程的地址空间和资源
创建开销 大(需要申请资源、创建地址空间) 小(只需在线程表中增加记录)
通信方式 进程间通信(IPC,如管道、消息队列、共享内存) 直接访问共享变量
运行方式 进程间相互独立 同一进程的线程可并发执行
适用场景 独立的任务,如浏览器的多个标签页 需要共享数据的任务,如多线程计算

1、多线程

1、理解

2、c/c++库函数

1、汇总

2、创建线程和获取线程ID

// 默认构造函,构造一个线程对象,在这个线程中不执行任何处理动作
thread() noexcept;
// 移动构造函数,将 other 的线程所有权转移给新的thread 对象。之后 other 不再表示执行线程。
thread( thread&& other ) noexcept;
// 创建线程对象,并在该线程中执行函数f中的业务逻辑,args是要传递给函数f的参数
template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );
// 使用=delete显示删除拷贝构造, 不允许线程对象之间的拷贝
thread( const thread& ) = delete;

std::thread::id get_id() const noexcept;

示例

/* 直接创建线程  */
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;

void func(int num, string str)
{
    for (int i = 0; i < 10; ++i)
    {
        cout << "子线程: i = " << i << "num: " 
             << num << ", str: " << str << endl;
    }
}
void func1()
{
    for (int i = 0; i < 10; ++i)
    {
        cout << "子线程: i = " << i << endl;
    }
}
int main()
{
    cout << "主线程的线程ID: " << this_thread::get_id() << endl;
    thread t(func, 520, "i love you");
    thread t1(func1);

    cout << "线程t 的线程ID: " << t.get_id() << endl;
    cout << "线程t1的线程ID: " << t1.get_id() << endl;

    t.join();
    t1.join();
}

/*  使用 lambda 表达式创建线程  */
#include <iostream>
#include <thread>

int main() {
    std::thread t([]() {
        std::cout << "Lambda 线程运行中" << std::endl;
    });

    t.join();  // 等待线程执行完毕
    return 0;
}

/* 移动构造函数:转移线程所有权 */
#include <iostream>
#include <thread>

void task() {
    std::cout << "线程运行中" << std::endl;
}

int main() {
    std::thread t1(task);  // 创建线程 t1
    std::thread t2 = std::move(t1);  // 转移线程所有权

    if (!t1.joinable()) {
        std::cout << "t1 现在是空的" << std::endl;
    }

    t2.join();  // 等待 t2 线程执行完毕
    return 0;
}

3、join与detach方式

区别

  • detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
  • join方式,等待启动的线程完成,才会继续往下执行。
//joinable()这个函数可以判断一个线程是使用的join,还是detach
// return true/false  

4、this_thread

函数 使用 说明
get_id std::this_thread::get_id() 获取线程id
yield std::this_thread::yield() 放弃线程执行,回到就绪状态
sleep_for std::this_thread::sleep_for(std::chrono::seconds(1)); 暂停1秒
sleep_until 指定休眠到一个时间
#include <iostream>
#include <thread>
#include <chrono>

using namespace std;
void my_thread()
{
    std::cout << "Thread " << std::this_thread::get_id() << " start!" << std::endl;
    std::this_thread::yield();  // 让出当前线程的时间片
    std::this_thread::sleep_for(std::chrono::milliseconds(200));  // 线程休眠200毫秒
    std::cout << "Thread " << std::this_thread::get_id() << " end!" << std::endl;
}

int main()
{
    std::cout << "Main thread id: " << std::this_thread::get_id() << std::endl;

    std::thread t1(my_thread);
    std::thread t2(my_thread);

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

// 使用 sleep_until 休眠一秒
void sleep_0nes()
{
    using std::chrono::system_clock;
    std::time_t tt = system_clock::to_time_t(system_clock::now());
    struct std::tm * ptm = std::localtime(&tt);
    cout << "Waiting for the next minute to begin...\n";
    ++ptm->tm_min; //加一分钟
    ptm->tm_sec = 0; //秒数设置为0//暂停执行,到下一整分执行
    this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));    
}

5、互斥锁

多线程会出现资源共享的情况所以要注意:

  • 线程之间的共享数据访问需要进行同步,以防止数据竞争和其他问题。可以使用互斥量条件变量等机制进行同步。
  • 可能会发生死锁问题,即多个线程互相等待对方释放锁,导致程序无法继续执行。
  • 可能会发生竞态条件问题,即多个线程执行的顺序导致结果的不确定性。
类型 说明
std::mutex 基本类
std::recursive_mutex 递归Mutex类
std::time_mutex 定时 Mutex 类
std::recursive_timed_mutex 定时递归 Mutex 类

1、mutex

1、Lock and Unlock
方法 说明
lock() 资源上锁
unlock() 解锁资源
try_lock() 尝试上锁,如果未被上锁则上锁返回true,否则返回 false
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
int num = 0;

void thread_func(int& n)
{
    for (int i = 0; i < 10; ++i)
    {
        mtx.lock();
        n++;
        cout << "n: " << n << endl;;
        mtx.unlock();
    }
}

int main()
{
    std::thread myThread[10];
    for (std::thread& a : myThread)
    {
        a = std::thread(thread_func, std::ref(num));
        a.join();
    }

    std::cout << "num = " << num << std::endl;
    std::cout << "Main thread exits!" << std::endl;
    return 0;
}
2、lock_guard

std::lock_guard是C++标准库中的一个模板类,用于实现资源的自动加锁和解锁。

  • 自动加锁: 在创建std::lock_guard对象时,会立即对指定的互斥量进行加锁操作。这样可以确保在进入作用域后,互斥量已经被锁定,避免了并发访问资源的竞争条件。
  • 自动解锁:std::lock_guard对象在作用域结束时,会自动释放互斥量。无论作用域是通过正常的流程结束、异常抛出还是使用return语句提前返回,std::lock_guard都能保证互斥量被正确解锁,避免了资源泄漏和死锁的风险。
  • 适用于局部锁定: 由于std::lock_guard是通过栈上的对象实现的,因此适用于在局部范围内锁定互斥量。当超出std::lock_guard对象的作用域时,互斥量会自动解锁,释放控制权。
#include <iostream>
#include <thread>
#include <chrono>
using namespace std;
#include <mutex>
int num = 0;
void thread_func(int& n)
{
    std::lock_guard<std::mutex> lock(mtx);  // 加锁互斥量
    std::cout << "n: " << n++ << std::endl;
    // 执行需要加锁保护的代码
}  

int main()
{
    std::thread myThread[10];
    for (std::thread& a : myThread)
    {
        a = std::thread(thread_func, std::ref(num));
        a.join();
    }
    std::cout << "num == " << num << std::endl;
    return 0;
}
3、unique_lock
  • 自动加锁和解锁: 与std::lock_guard类似,std::unique_lock在创建对象时立即对指定的互斥量进行加锁操作,确保互斥量被锁定。在对象的生命周期结束时,会自动解锁互斥量。这种自动加锁和解锁的机制避免了手动管理锁的复杂性和可能出现的错误。
  • 支持灵活的加锁和解锁: 相对于std::lock_guard的自动加锁和解锁,std::unique_lock提供了更灵活的方式。它可以在需要的时候手动加锁和解锁互斥量,允许在不同的代码块中对互斥量进行多次加锁和解锁操作。
  • 支持延迟加锁和条件变量:std::unique_lock还支持延迟加锁的功能,可以在不立即加锁的情况下创建对象,稍后根据需要进行加锁操作。此外,它还可以与条件变量(std::condition_variable)一起使用,实现更复杂的线程同步和等待机制。
#include <mutex>

std::mutex mtx;  // 互斥量

void thread_function()
{
    std::unique_lock<std::mutex> lock(mtx);  // 加锁互斥量
    std::cout << "Thread running" << std::endl;
    // 执行需要加锁保护的代码

    lock.unlock();  // 手动解锁互斥量
    // 执行不需要加锁保护的代码

    lock.lock();  // 再次加锁互斥量
    // 执行需要加锁保护的代码
}
// unique_lock对象的析构函数自动解锁互斥量

int main()
{
    std::thread t1(thread_function);
    t1.join();
    std::cout << "Main thread exits!" << std::endl;
    return 0;
}

2、recursive_mutex

递归互斥锁std::recursive_mutex允许同一线程多次获得互斥锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

struct Calculate
{
    Calculate() : m_i(6) {}

    void mul(int x)
    {
        lock_guard<mutex> locker(m_mutex);
        m_i *= x;
    }

    void div(int x)
    {
        lock_guard<mutex> locker(m_mutex);
        m_i /= x;
    }

    void both(int x, int y)
    {
        lock_guard<mutex> locker(m_mutex);
        mul(x);
        div(y);
    }

    int m_i;
    mutex m_mutex;
};

int main()
{
    Calculate cal;
    cal.both(6, 3);
    return 0;
}

/* 上面程序再运行会出现死锁的问题,因为both函数已经上锁然后调用mul又要上锁,这样就是死锁永远解不开  */

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

struct Calculate
{
    Calculate() : m_i(6) {}

    void mul(int x)
    {
        lock_guard<recursive_mutex> locker(m_mutex);
        m_i *= x;
    }

    void div(int x)
    {
        lock_guard<recursive_mutex> locker(m_mutex);
        m_i /= x;
    }

    void both(int x, int y)
    {
        lock_guard<recursive_mutex> locker(m_mutex);
        mul(x);
        div(y);
    }

    int m_i;
    recursive_mutex m_mutex;
};

int main()
{
    Calculate cal;
    cal.both(6, 3);
    cout << "cal.m_i = " << cal.m_i << endl;
    return 0;
}

/* 所以替换调mutex  使用recursive_mutex 可以解决这个问题 */

注意

  • 递归锁效率低
  • 这个按逻辑上是不行的,会产生bug

3、timed_mutex

超时互斥锁,就是设定超时时间,如果超时还没有解锁将自动解锁。

void lock();
bool try_lock();
void unlock();

// std::timed_mutex比std::_mutex多出的两个成员函数
template <class Rep, class Period>
  bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);

template <class Clock, class Duration>
  bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);
  • try_lock_for函数是当线程获取不到互斥锁资源的时候,让线程阻塞一定的时间长度
  • try_lock_until函数是当线程获取不到互斥锁资源的时候,让线程阻塞到某一个指定的时间点
  • 关于两个函数的返回值:当得到互斥锁的所有权之后,函数会马上解除阻塞,返回true如果阻塞的时长用完或者到达指定的时间点之后,函数也会解除阻塞,返回false
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

timed_mutex g_mutex;

void work()
{
    chrono::seconds timeout(1);
    while (true)
    {
        // 通过阻塞一定的时长来争取得到互斥锁所有权
        if (g_mutex.try_lock_for(timeout))
        {
            cout << "当前线程ID: " << this_thread::get_id() 
                << ", 得到互斥锁所有权..." << endl;
            // 模拟处理任务用了一定的时长
            this_thread::sleep_for(chrono::seconds(10));
            // 互斥锁解锁
            g_mutex.unlock();
            break;
        }
        else
        {
            cout << "当前线程ID: " << this_thread::get_id() 
                << ", 没有得到互斥锁所有权..." << endl;
            // 模拟处理其他任务用了一定的时长
            this_thread::sleep_for(chrono::milliseconds(50));
        }
    }
}

int main()
{
    thread t1(work);
    thread t2(work);

    t1.join();
    t2.join();

    return 0;
}

注意: 会有跟recursive_mutex一样的问题,少用

6、条件变量

在多线程编程中,条件变量(std::condition_variable 是用于线程间同步的机制,主要用于线程等待某个条件达成后再继续执行
条件变量可以让线程阻塞等待,直到某个条件满足,从而避免轮询(忙等)导致的 CPU 资源浪费。

C++11 提供了 std::condition_variable 来实现线程同步,它通常与 std::mutexstd::unique_lock 搭配使用。
主要组件:

  1. std::condition_variable:条件变量对象,提供 wait()notify_*() 等方法。
  2. std::mutex:互斥锁,保护共享数据的访问。
  3. std::unique_lock<std::mutex>:锁对象,提供 wait() 时自动释放锁并等待的能力。

条件变量用于生产者消费者模型中:

  • 生产者线程 生成数据并通知消费者。

  • 消费者线程 等待数据并消费。

1、wait()

函数 作用
wait(lock) 等待通知,释放锁,通知后重新获取锁
wait(lock, 条件) 等待并在条件满足时返回
wait_for(lock, timeout, 条件) 超时等待,如果超时仍未满足条件,则返回
wait_until(lock, time_point, 条件) 等待直到指定时间点,若超时则返回
void wait(std::unique_lock<std::mutex>& lock);
template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);    //该版本会在 pred() 返回 true 时立即返回,否则一直等待。 可以使用lambda 表达式
  • 用于阻塞当前线程,直到收到通知或者满足特定条件。

  • 释放锁并阻塞,在收到通知时自动尝试重新获取锁。

/*  wait_for() 示例 */
if (cond_var.wait_for(lock, std::chrono::seconds(5), [] { return !dataQueue.empty(); })) {
    std::cout << "成功等待到数据" << std::endl;
} else {
    std::cout << "超时,没有数据" << std::endl;
}

2、notify_one()

唤醒一个等待的线程(如果有多个线程等待,则随机选择一个)

void notify_one();

3、notify_all()

void notify_all();

4、示例

  • 消费者线程 先运行,检查 dataQueue 是否有数据。

  • 如果 dataQueue 为空,它会 释放锁并等待

  • 生产者线程 生成数据,并调用 notify_one() 通知消费者。

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

std::queue<int> dataQueue;  // 共享队列
std::mutex mtx;  // 互斥锁
std::condition_variable cond_var;  // 条件变量
bool done = false;  // 是否结束的标志位

// 消费者线程
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // wait() 会释放锁,并在条件满足时重新获取锁
        cond_var.wait(lock, [] { return !dataQueue.empty() || done; });

        if (done && dataQueue.empty()) break; // 退出条件

        int value = dataQueue.front();
        dataQueue.pop();
        std::cout << "消费数据: " << value << std::endl;

        lock.unlock();  // 手动释放锁(可省略)
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 模拟处理时间
    }
}

// 生产者线程
void producer() {
    for (int i = 1; i <= 10; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            dataQueue.push(i);
            std::cout << "生产数据: " << i << std::endl;
        }
        cond_var.notify_one();  // 通知消费者
        std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 模拟生产时间
    }

    {
        std::lock_guard<std::mutex> lock(mtx);
        done = true;  // 设定结束标志
    }
    cond_var.notify_all(); // 通知所有等待的消费者线程
}

int main() {
    std::thread t1(consumer);
    std::thread t2(producer);

    t1.join();
    t2.join();

    std::cout << "所有数据处理完毕" << std::endl;
    return 0;
}

3、线程池

任务队列(生产者-消费者模型)

  • 任务存储结构
  • 任务的添加与移除
  • 线程安全的任务获取

工作线程(多个线程从任务队列取任务执行)

  • 线程阻塞等待任务
  • 任务执行完后继续等待新任务
  • 无任务时阻塞,有任务时唤醒

管理线程(动态调整工作线程数量)

  • 周期性检查任务队列
  • 任务过多时增加工作线程
  • 任务过少时减少空闲线程
#include <iostream>
#include <queue>
#include <vector>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <atomic>

class ThreadPool {
public:
    // 线程池构造函数,初始化最小和最大线程数
    ThreadPool(size_t minThreads, size_t maxThreads);
    // 线程池析构函数,清理资源
    ~ThreadPool();
    // 向任务队列中添加任务
    void enqueueTask(std::function<void()> task);

private:
    // 工作线程函数,不断从任务队列中取任务执行
    void workerThread();
    // 管理者线程函数,周期性调整线程数量
    void managerThread();
    // 根据任务量动态调整线程池大小
    void adjustThreadCount();

    std::queue<std::function<void()>> taskQueue; // 任务队列
    std::vector<std::thread> workers; // 工作线程列表
    std::thread manager; // 管理者线程
    std::mutex queueMutex; // 保护任务队列的互斥锁
    std::condition_variable condition; // 条件变量,用于线程同步
    std::atomic<bool> stop; // 控制线程池是否终止

    size_t minThreads; // 线程池的最小线程数
    size_t maxThreads; // 线程池的最大线程数
    std::atomic<size_t> busyThreads; // 记录当前忙碌的线程数
};

ThreadPool::ThreadPool(size_t minThreads, size_t maxThreads)
    : stop(false), minThreads(minThreads), maxThreads(maxThreads), busyThreads(0) {
    // 预创建最小数量的工作线程
    for (size_t i = 0; i < minThreads; ++i) {
        workers.emplace_back(&ThreadPool::workerThread, this);
    }
    // 启动管理者线程
    manager = std::thread(&ThreadPool::managerThread, this);
}

ThreadPool::~ThreadPool() {
    // 设置停止标志,通知所有线程退出
    stop = true;
    condition.notify_all();

    // 等待所有工作线程退出
    for (std::thread &worker : workers) {
        if (worker.joinable()) worker.join();
    }
    // 等待管理者线程退出
    if (manager.joinable()) manager.join();
}

void ThreadPool::enqueueTask(std::function<void()> task) {
    {
        std::lock_guard<std::mutex> lock(queueMutex); // 保护任务队列
        taskQueue.push(task); // 添加任务
    }
    condition.notify_one(); // 唤醒一个等待的工作线程
}

void ThreadPool::workerThread() {
    while (!stop) {
        std::function<void()> task;
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            condition.wait(lock, [this] { return stop || !taskQueue.empty(); });
            if (stop && taskQueue.empty()) return; // 如果停止且任务队列为空,则退出
            task = taskQueue.front(); // 取出任务
            taskQueue.pop(); // 移除任务
            ++busyThreads; // 标记为忙碌线程
        }
        task(); // 执行任务
        --busyThreads; // 任务完成,标记为空闲线程
    }
}

void ThreadPool::managerThread() {
    while (!stop) {
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 休眠一段时间
        adjustThreadCount(); // 调整线程池大小
    }
}

void ThreadPool::adjustThreadCount() {
    std::lock_guard<std::mutex> lock(queueMutex);
    // 如果任务数量超过当前线程数,并且未达到最大线程数,则增加线程
    if (taskQueue.size() > workers.size() && workers.size() < maxThreads) {
        workers.emplace_back(&ThreadPool::workerThread, this);
    }
    // 如果线程池线程数大于最小值,且空闲线程过多,则减少线程
    else if (workers.size() > minThreads && busyThreads < workers.size() / 2) {
        stop = true;
        condition.notify_all(); // 通知所有线程检查停止标志
        workers.pop_back(); // 移除最后一个线程
        stop = false;
    }
}

int main() {
    ThreadPool pool(2, 4); // 创建线程池,最小2个线程,最大4个线程
    for (int i = 0; i < 10; ++i) {
        pool.enqueueTask([i] { std::cout << "Task " << i << " executed." << std::endl; });
    }
    std::this_thread::sleep_for(std::chrono::seconds(5)); // 等待任务完成
    return 0;
}

4、多线程的异步操作

std::async 是 C++11 中用于异步编程的一个标准库函数,它启动一个异步任务并返回一个 std::future 对象,用来获取任务的结果。

签名函数

std::future<T> std::async(
    std::launch policy, 
    Callable&& f, 
    Args&&... args
);
  • std::launch policy:指定并发策略,决定任务是立即在新线程中执行还是延迟执行。

    • std::launch::async:异步执行,通常会在新的线程中执行任务。

    • std::launch::deferred:延迟执行,任务会在调用 get()wait() 时执行。

    • 如果不指定,则默认由实现决定如何执行。

  • Callable&& f:要异步执行的函数或可调用对象。可以是函数指针、函数对象(std::function),甚至是 Lambda 表达式。

  • Args&&... args:传递给函数 f 的参数,std::async 会将这些参数转发给 f。这个参数包可以接受任意数量和类型的参数。

std::future 类型

std::async 返回一个 std::future 对象,它表示异步操作的结果。std::future 提供了以下方法:

  • get():等待任务完成并返回结果。如果任务抛出异常,get() 会重新抛出该异常。
  • valid():返回 true 如果 std::future 是有效的,通常是任务还没有完成时。
  • wait():阻塞当前线程直到任务完成。
  • wait_for()wait_until():分别在指定时间内等待任务完成或直到某个时间点。

示例

#include <iostream>
#include <future>

int add(int a, int b) {
    return a + b;
}

int main() {
    // 启动一个异步任务
    std::future<int> result = std::async(std::launch::async, add, 5, 7);

    // 在主线程中做一些其他工作
    std::cout << "Main thread doing some work..." << std::endl;

    // 获取异步任务的结果
    int sum = result.get(); // 会阻塞,直到任务完成
    std::cout << "Result: " << sum << std::endl;

    return 0;
}

/* 使用 std::launch::deferred 延迟执行 */
#include <iostream>
#include <future>

int square(int x) {
    std::cout << "Calculating square of " << x << "...\n";
    return x * x;
}

int main() {
    // 使用 std::launch::deferred 延迟执行任务
    std::future<int> result = std::async(std::launch::deferred, square, 5);

    // 在此时,square 函数并未执行
    std::cout << "Doing other work...\n";

    // 只有在调用 get() 时,任务才会执行
    int res = result.get(); // 任务在此处执行
    std::cout << "Square result: " << res << std::endl;

    return 0;
}

/* 判断返回类型 */
#include <iostream>
#include <thread>
#include <future>
using namespace std;

int main()
{
    cout << "主线程ID: " << this_thread::get_id() << endl;
    // 调用函数直接创建线程执行任务
    future<int> f = async([](int x) {
        cout << "子线程ID: " << this_thread::get_id() << endl;
        this_thread::sleep_for(chrono::seconds(5));
        return x += 100;
    }, 100);

    future_status status;
    do {
        status = f.wait_for(chrono::seconds(1));
        if (status == future_status::deferred)
        {
            cout << "线程还没有执行..." << endl;
            f.wait();
        }
        else if (status == future_status::ready)
        {
            cout << "子线程返回值: " << f.get() << endl;
        }
        else if (status == future_status::timeout)
        {
            cout << "任务还未执行完毕, 继续等待..." << endl;
        }
    } while (status != future_status::ready);

    return 0;
}

最终总结:

  • 使用async()函数,是多线程操作中最简单的一种方式,不需要自己创建线程对象,并且可以得到子线程函数的返回值。
  • 使用std::promise类,在子线程中可以传出返回值也可以传出其他数据,并且可选择在什么时机将数据从子线程中传递出来,使用起来更灵活。
  • 使用std::packaged_task类,可以将子线程的任务函数进行包装,并且可以得到子线程的返回值。

2、多进程

1、进程概述

进程(Process)是程序在操作系统中的一次运行实例,是资源分配的基本单位,每个进程都有自己独立的地址空间、代码、数据和系统资源,多个进程之间相互独立,但可以通过进程间通信(IPC)进行交互。

1、PCB控制块

进程控制块(PCB, Process Control Block)是操作系统用于管理进程的数据结构。
每个进程在创建时,操作系统都会为其分配一个 PCB,用于存储进程的各种信息,如:

  • 进程的标识信息(进程 ID)
  • 进程的状态(运行、就绪、阻塞等)
  • 进程的资源(CPU、内存、文件等)
  • 进程的调度信息(优先级、时间片等)

PCB 是进程存在的唯一标志,进程的所有操作(创建、调度、终止等)都由操作系统通过 PCB 进行管理。

类别 主要内容
进程标识信息 进程 ID(PID)、父进程 ID(PPID)、用户 ID(UID)、组 ID(GID)
进程状态 运行(Running)、就绪(Ready)、阻塞(Blocked)等
进程调度信息 进程优先级、调度策略、时间片
CPU 寄存器 程序计数器(PC)、通用寄存器、堆栈指针(SP)
内存管理信息 代码段、数据段、堆栈段的起始地址、页表信息
文件管理信息 打开的文件描述符(文件表)、当前目录
进程间通信(IPC)信息 信号、管道、消息队列、共享内存
资源使用信息 CPU 时间、内存占用、I/O 设备信息

操作系统如何管理进程?

  • 进程创建时,分配一个 PCB,并初始化相关信息。
  • 进程运行时CPU 通过 PCB 读取 程序计数器(PC),执行指令。
  • 进程切换时,操作系统 保存当前进程的 PCB,然后 加载下一个进程的 PCB。
  • 进程终止时,释放 PCB,并回收相关资源。

示例:进程切换时的 PCB 变化

  • 进程 A 正在运行(CPU 寄存器、程序计数器等信息存储在 PCB_A)。
  • 发生进程切换(比如时间片到期)。
  • 操作系统保存进程 A 的 PCB_A 信息到内存。
  • 操作系统加载进程 B 的 PCB_B 信息到 CPU。
  • 进程 B 继续执行。

2、进程状态

进程一共有五种状态分别为:创建态,就绪态,运行态,阻塞态(挂起态),退出态(终止态)。

状态转换 触发条件
新建 → 就绪 进程创建完成,等待 CPU 分配
就绪 → 运行 进程被调度程序选中,获取 CPU。
运行 → 就绪 时间片耗尽,进程被换出 CPU
运行 → 阻塞 进程等待 I/O、信号或资源(如用户输入)。
阻塞 → 就绪 进程等待的事件发生(如 I/O 完成)。
运行 → 终止 进程正常执行完成或被终止(如 exit() 调用)。

2、进程相关函数

函数作用

函数 作用
fork() 创建子进程,子进程是父进程的副本。
exec() 用新程序替换当前进程。
wait() 父进程等待子进程结束。
exit() 进程退出。
getpid() 获取当前进程的 PID。
getppid() 获取父进程的 PID。
kill() 发送信号给进程(如终止)。

创建子进程

#include <iostream>
#include <unistd.h>  // fork, getpid, getppid
#include <sys/wait.h>  // wait

int main() {
    pid_t pid = fork();  // 创建子进程

    if (pid == 0) {  // 子进程
        std::cout << "子进程: PID=" << getpid() << ", 父进程 PID=" << getppid() << std::endl;
        sleep(2);  // 让子进程运行一段时间
        std::cout << "子进程结束\n";
    } 
    else if (pid > 0) {  // 父进程
        std::cout << "父进程: PID=" << getpid() << ", 创建了子进程 PID=" << pid << std::endl;
        wait(NULL);  // 等待子进程结束
        std::cout << "父进程结束\n";
    } 
    else {
        std::cerr << "fork 失败!\n";
    }

    return 0;
}

/*
父进程: PID=1234, 创建了子进程 PID=1235
子进程: PID=1235, 父进程 PID=1234
子进程结束
父进程结束
*/

exec() 执行新程序

//exec() 系列函数用于替换当前进程的代码,通常用于加载新程序。
#include <iostream>
#include <unistd.h>

int main() {
    std::cout << "当前进程 PID: " << getpid() << ",即将执行 ls 命令...\n";

    execlp("ls", "ls", "-l", NULL);  // 替换当前进程执行 ls 命令

    std::cerr << "exec 失败!\n";  // 如果 exec 成功,不会执行到这里
    return 1;
}

/*
当前进程 PID: 3394340,即将执行 ls 命令...
total 96
-rwxr-xr-x 1 root root 17096 Mar 12 14:53 a.out
-rw-r--r-- 1 root root  2800 Mar 12 09:04 tcp_server_.cpp
-rwxr-xr-x 1 root root 17360 Mar 14 14:40 thread_test
-rw-r--r-- 1 root root   406 Mar 14 14:40 thread_test.cpp
-rwxr-xr-x 1 root root 17168 Mar 12 15:02 udp_client
-rw-r--r-- 1 root root  1062 Mar 12 15:02 udp_client.c
-rwxr-xr-x 1 root root 17144 Mar 12 15:00 udp_server
-rw-r--r-- 1 root root  1329 Mar 12 15:00 udp_server.c
*/

创建僵尸进程

//僵尸进程(`Z` 状态)是子进程结束但**父进程没有回收**导致的
#include <iostream>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        std::cout << "子进程: PID=" << getpid() << " 运行中...\n";
        sleep(2);
        std::cout << "子进程退出\n";
    } 
    else if (pid > 0) {
        std::cout << "父进程: PID=" << getpid() << " 但不会等待子进程\n";
        sleep(10);  // 让子进程变成僵尸进程
    }

    return 0;
}

运行 ps -aux | grep Z 可以看到僵尸进程

博客内容均系原创,未经允许严禁转载!
您可以通过 RSS 订阅本站文章更新,订阅地址:https://blognas.hwb0307.com/feed/什么是 RSS ?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇