第四章学习笔记

第四章学习笔记

知识点总结

1.并发与并行:

  • 并发是多个任务在同一时间段内交替执行,而不一定是同时执行。这通常是通过操作系统的时间片轮转来实现的,各个任务分时共享处理器。
  • 并行是多个任务真正同时执行,通常需要多个处理器核心。这可以显著提高性能,但需要更复杂的硬件和软件支持。

2.线程和进程:

  • 线程是程序内的执行单元,多个线程可以在同一进程内并发执行。它们共享相同的内存空间,因此可以轻松地共享数据。
  • 进程是独立的执行环境,每个进程都有自己的内存空间和资源。不同进程之间的通信通常需要额外的机制,如进程间通信(IPC)。

3.同步与异步:

  • 同步操作是按顺序执行,一个操作完成后才执行下一个。同步通常使用互斥锁来保护共享资源,以防止多个线程同时访问。
  • 异步操作是可以同时执行,不需要等待前一个操作完成。异步编程通常使用回调、Promise、async/await等机制来处理。

4.互斥和锁:

  • 互斥是一种用于保护共享资源的技术,以防止多个线程同时修改它。最常见的实现方式是互斥锁,它只允许一个线程访问被锁定的资源,其他线程必须等待。
  • 除了互斥锁,还有信号量、读写锁等不同类型的锁,每种锁适用于不同的并发场景。

5.死锁:

  • 死锁是一种并发编程错误,它发生在多个线程或进程相互等待对方释放资源的情况下。这导致所有任务都无法继续执行,造成系统僵死。
  • 死锁的预防和解决通常涉及到合理的资源分配、按序加锁、超时机制、死锁检测和解除等策略。

6.条件变量:

  • 条件变量是用于线程间通信和同步的重要工具。它们允许线程等待某个条件的发生,然后才继续执行。
  • 条件变量通常与互斥锁结合使用,等待线程在互斥锁上休眠,直到其他线程满足条件并发出信号,唤醒等待的线程。
  • 条件变量在解决生产者-消费者问题、读者-写者问题等场景中非常有用。

7.并发数据结构:

  • 并发数据结构是专门设计用于多线程或多进程环境的数据结构,以确保线程安全的数据操作。
  • 例如,并发队列(如Java的ConcurrentLinkedQueue)、并发映射(如Java的ConcurrentHashMap)允许多个线程同时访问数据而不会导致竞态条件。
  • 这些数据结构通常使用锁、原子操作或其他同步机制来确保线程安全性。

8.原子操作:

  • 原子操作是不可分割的操作,即它们要么完全执行,要么根本不执行。这保证了数据的一致性。
  • 原子操作通常由硬件支持,例如在现代处理器上的CAS(Compare-And-Swap)指令,或者通过锁机制来实现。
  • 原子操作对于并发计数、信号量、自旋锁等非常有用。

9.线程池:

  • 线程池是一组预先创建的线程,用于执行任务队列中的任务。这可以减少线程的创建和销毁开销,提高系统性能。
  • 线程池通常包括一个任务队列和一组工作线程,任务被提交到队列中,线程池中的线程按需取出并执行这些任务。
  • 线程池管理线程的生命周期,可以控制并发度,避免过多线程同时运行。

10.并发模型:

  • 并发模型是一种用于描述如何处理并发任务的方法。不同的并发模型适用于不同类型的问题和应用场景。
  • 常见的并发模型包括多线程模型、多进程模型、事件驱动模型和消息传递模型。
  • 选择适当的并发模型取决于任务的性质和要解决的问题,例如,多线程模型适合CPU密集型任务,而事件驱动模型适合I/O密集型任务。

苏格拉底挑战




问题及解决方案

1.竞态条件:

  • 问题:多个线程同时访问共享资源,可能导致不确定的结果。
  • 解决方案:使用互斥锁或其他同步机制来保护共享资源,以确保只有一个线程可以访问。

2.死锁:

  • 问题:多个线程相互等待彼此持有的资源,导致所有线程无法继续执行。
  • 解决方案:遵循资源请求的有序性、超时等策略,或使用死锁检测和解除机制。

3.饥饿:

  • 问题:某些线程长时间无法获得所需的资源,导致它们无法执行。
  • 解决方案:采用公平的资源分配策略,确保每个线程都有机会访问资源。

4.活锁:

  • 问题:线程一直重试某个操作,但无法进展,通常是由于线程之间的过度协作而引起。
  • 解决方案:引入随机性或策略变化,以打破协作循环。

5.数据竞争:

  • 问题:多个线程同时修改共享数据,没有适当的同步措施,可能导致数据损坏和应用程序崩溃。
  • 解决方案:使用互斥锁、原子操作、事务性内存等来确保数据访问的原子性。

6.性能瓶颈:

  • 问题:过多的锁使用、线程间频繁通信等可能导致性能下降。
  • 解决方案:优化并发数据结构、减少锁的使用、采用无锁数据结构,以提高性能。

7.线程泄漏:

  • 问题:线程没有正确终止,导致资源泄漏。
  • 解决方案:确保线程在不再需要时正确终止,或使用线程池来管理线程的生命周期。

实践过程

演示了如何在多线程环境中使用 POSIX 线程库进行并发编程。这个示例创建两个线程,每个线程都执行不同的任务,用于演示线程的创建和同步。

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

// 线程函数1
void* thread_function1(void* arg) {
    for (int i = 1; i <= 5; i++) {
        printf("Thread 1: Counter %d\n", i);
        sleep(1);  // 模拟工作
    }
    pthread_exit(NULL);
}

// 线程函数2
void* thread_function2(void* arg) {
    for (int i = 1; i <= 5; i++) {
        printf("Thread 2: Counter %d\n", i);
        sleep(1);  // 模拟工作
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;
    
    // 创建线程1
    if (pthread_create(&thread1, NULL, thread_function1, NULL) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 创建线程2
    if (pthread_create(&thread2, NULL, thread_function2, NULL) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程1和线程2完成
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    printf("Both threads have completed.\n");
    
    return 0;
}


在这个示例中,我们创建了两个线程,每个线程都执行一个简单的计数任务,然后休眠 1 秒以模拟工作。最后,我们等待两个线程完成。

posted @ 2023-10-29 16:11  20211308wjc  阅读(4)  评论(0编辑  收藏  举报