69天探索操作系统-第7天:深入了解线程概念 - 线程的基本概念

242 阅读5分钟

线程.avif

1.介绍

线程是现代计算中的一个基本概念,它在单个进程中实现多任务和并行操作。本文探讨了线程的基础知识、生命周期、实现模型以及与进程的不同之处。我们还将通过使用C语言和POSIX线程(Pthreads)进行实际示例。

2.什么是线程

定义

线程是进程中执行的最小单元。线程共享属于它们的进程的内存空间和资源,但可以独立执行

image.png

线程特性

  • 共享内存: 同一进程中的线程共享相同的地址空间,包括全局变量、堆和代码。
  • 独立执行:每个线程都有自己的程序计数器、栈和寄存器。
  • 轻量级: 由于线程共享资源并需要更少的开销来创建和上下文切换,因此比进程更轻量级。

线程VS进程

image.png

3. 为什么要使用线程?

多线程的好处

  1. 并发:线程允许同时运行多个任务,提高响应速度。
    • 示例:网络服务可以使用线程处理多个客户端请求。
  2. 资源共享:线程共享相同的内存和资源,减少重复。
  3. 可扩展性:线程可以利用多核处理器进行并行执行。
  4. 简化设计:线程简化了执行多任务的应用程序设计。

现实应用

  • Web Servers:同时处理多个客户端请求。
  • GUI Applications:保持界面响应,同时执行后台任务。
  • 科学计算:执行并行计算以获得更快的速度。

4. 线程生命周期

线程状态

一个线程在其生命周期中会经历以下状态:

  1. New:线程被创建但尚未开始运行。
  2. Runnable:线程已准备好运行,但正在等待CPU时间。
  3. Running:线程正在执行指令。
  4. Blocked/Waiting:线程正在等待资源或事件。
  5. Terminated:线程已完成执行。

状态过渡

  • 当调用 pthread_create() 时,从 New 状态过渡到可运行状态。
  • 当调度器分配 CPU 时间时,它会移动到正在运行状态。
  • 在等待 I/O 或同步时,它会进入阻塞状态。
  • 当完成执行或被主动停止时,它会过渡到已终止状态。

5. 线程实现模型

用户级线程

  • 完全在用户空间管理,没有内核参与。
  • 优点:
    • 创建速度更快,上下文切换更快。
    • 没有内核开销。
  • 缺点:
    • 无法利用多核 CPU。
    • 阻塞系统调用会阻塞整个进程。

内核级线程

  • 由操作系统内核管理。
  • 优势:
    • 可以在多个CPU上运行。
    • 阻塞系统调用不会阻塞其他线程。
  • 缺点:
    • 由于内核参与,运行开销较高。

6. 线程库和标准

POSIX 线程(Pthreads)

Pthreads 是 C 语言中线程编程的广泛使用标准。它提供了以下 API:

  • 线程创建和管理
  • 同步(互斥锁、条件变量)
  • 线程特定数据

Windows 线程

Windows 提供自己的线程 API,包括 CreateThread() 等函数以及 CriticalSection 等同步原语。

7.代码实现

在C中创建和管理线程

以下是使用Pthreads创建和管理线程的示例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* thread_function(void* arg) {
    int thread_id = *(int*)arg;
    printf("Thread %d is running\n", thread_id);
    return NULL;
}

int main() {
    pthread_t threads[5];
    int thread_ids[5];

    for (int i = 0; i < 5; i++) {
        thread_ids[i] = i + 1;
        if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
            perror("Failed to create thread");
            exit(1);
        }
    }

    for (int i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("All threads have finished execution\n");
    return 0;
}

线程之间的同步

线程通常需要同步对共享资源的访问。这里有一个使用互斥锁的例子:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_mutex_t lock;
int shared_counter = 0;

void* increment_counter(void* arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&lock);
        shared_counter++;
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_t threads[2];

    pthread_mutex_init(&lock, NULL);

    for (int i = 0; i < 2; i++) {
        if (pthread_create(&threads[i], NULL, increment_counter, NULL) != 0) {
            perror("Failed to create thread");
            exit(1);
        }
    }

    for (int i = 0; i < 2; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&lock);

    printf("Final counter value: %d\n", shared_counter);
    return 0;
}

8. 性能考虑因素

线程开销

  • 创建时间:线程比进程更快创建,但仍然会产生一些开销。
  • 上下文切换:线程之间的切换速度比进程快,但仍然涉及保存和恢复寄存器。

优化线程使用

  • 最小化同步:过度锁定时会导致内容竞争,降低性能。
  • 使用线程池:重用线程,而不是反复创建和销毁。
  • 避免超额订阅:不要创建比CPU核心数更多的线程。

9.进一步阅读

  1. "Programming with POSIX Threads" by David R. Butenhof
  2. "Modern Operating Systems" by Andrew S. Tanenbaum
  3. Pthreads Documentation: man7.org/linux/man-p…
  4. Intel Threading Building Blocks: www.intel.com/content/www…

10.总结

线程是现代应用程序中实现并发和并行的重要工具。通过了解线程的生命周期、实现模型和同步机制,开发者可以构建高效、可扩展的多线程程序。虽然线程引入了一些复杂性,但适当的设计和优化可以释放其全部潜能。

11. 参考文献

  1. Butenhof, D. R. (1997). Programming with POSIX Threads. Addison-Wesley.
  2. Tanenbaum, A. S. (2014). Modern Operating Systems (4th ed.). Pearson.
  3. Linux Programmer's Manual: man7.org/linux/man-p…
  4. Intel Corporation. (2021). Intel® 64 and IA-32 Architectures Software Developer's Manual.