【Linux操作系统】线程的基本知识和创建--循环创建多个子线程

本篇文章主要介绍了线程的概念和作用,线程三级映射的实现,创建线程的方法(讲解pthread_self和pthread_create函数),循环创建多个子线程为例子,同时分析线程之间的全局变量的共享问题,希望可以帮助你。

在这里插入图片描述


一、线程的概念及其作用

在计算机科学中,线程是进程中的一个执行单元。一个进程可以拥有多个线程,每个线程都可以独立执行不同的任务。线程是实现并发执行的重要工具,可以提高程序的效率和性能。

作用:

  1. 提高程序的响应性:通过多线程可以同时执行多个任务,提高程序的响应速度,避免因为一个任务的阻塞而导致整个程序的停顿。

  2. 提高计算机资源的利用率:多线程可以充分利用计算机的多核处理器,同时执行多个任务,提高计算机资源的利用率。

  3. 实现并发编程:线程可以用于实现并发编程,例如在服务器中同时处理多个客户端请求,或者在图形界面程序中同时响应用户的输入等。

  4. 实现任务的分解和协作:通过将一个复杂任务分解为多个子任务,每个子任务由一个线程执行,可以提高程序的可维护性和可扩展性。线程之间可以通过共享内存或消息传递等方式进行协作和通信。

在这里插入图片描述


三级映射

在操作系统中,线程的运行需要通过三级映射来实现。这三级映射包括虚拟地址到物理地址的映射、物理地址到主存的映射、以及主存到磁盘的映射。

  1. 虚拟地址到物理地址的映射:每个线程都有自己的虚拟地址空间,虚拟地址是线程在运行时使用的地址。操作系统通过页表来实现虚拟地址到物理地址的映射。页表将虚拟地址划分为一系列的页面,并将这些页面映射到物理地址上的页框。当线程访问虚拟地址时,操作系统会根据页表将虚拟地址转换为物理地址。

  2. 物理地址到主存的映射:物理地址是实际的硬件地址,表示计算机中的内存地址。操作系统管理物理地址和主存之间的映射关系,确保线程可以访问到正确的主存地址。操作系统会将物理地址分配给不同的线程,以便线程可以在主存中存储和访问数据。

  3. 主存到磁盘的映射:主存是计算机中的临时存储器,用于存储线程运行时所需的数据和指令。但是主存的容量是有限的,当主存不足以存储所有线程需要的数据时,操作系统会将部分数据存储到磁盘上的虚拟内存中。这样,当线程需要访问被存储在磁盘上的数据时,操作系统会将数据从磁盘读取到主存中,然后线程可以继续访问这些数据。


二、线程的共享和非共享

共享线程

共享线程是指多个线程可以访问和修改同一进程的共享资源。共享线程通常用于多个线程之间需要共享数据和进行协作的场景。

在共享线程中,多个线程可以同时读取和修改同一份共享数据。这种共享数据可以是 全局变量、静态变量、堆内存中的对象等 。共享线程可以通过对共享资源的读写操作来实现线程之间的通信和协作。

共享线程的优点是可以方便地共享数据和协作,但同时也需要注意线程安全的问题。多个线程同时读写共享资源可能会导致数据的不一致性和竞态条件等问题,需要通过加锁、使用同步机制等方式来保证共享资源的正确性和一致性。

非共享线程

非共享线程是指每个线程拥有自己独立的资源,其他线程无法访问和修改。非共享线程通常用于执行独立的任务,不需要与其他线程进行通信和共享数据。

在非共享线程中,每个线程拥有自己独立的 栈空间、寄存器 等资源。这意味着每个线程都可以独立地执行自己的任务,互不干扰。非共享线程的优点是可以提高程序的并发性和执行效率,但同时也限制了线程之间的通信和协作。


三、创建线程的方法

相关函数

我们先来认识两个关于创建线程的函数:pthread_selfpthread_create


1. pthread_self():

pthread_self函数作用是获取当前线程的线程ID

函数原型:

pthread_t pthread_self(void);

该函数没有参数。

返回值:
返回调用线程的线程ID。


2. pthread_create

pthread_create函数的作用是创建一个新的线程,并将其加入到进程中。通过该函数,我们可以实现多线程的并发执行。

函数原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

参数:

  • thread:指向线程ID的指针,用于存储新创建的线程的ID。
  • attr:指向线程属性的指针,用于设置新创建的线程的属性。可以传入NULL,使用默认属性。
  • start_routine:指向线程函数的指针,新创建的线程将从该函数开始执行。
  • arg:传递给线程函数的参数,可以是任意类型的指针。

返回值:
成功创建线程时,返回0;创建线程失败时,返回一个非零的错误代码。

示例和解释:

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

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

int main() {
    
    
    pthread_t thread1, thread2;
    int arg1 = 1, arg2 = 2;

    pthread_create(&thread1, NULL, thread_func, &arg1);
    pthread_create(&thread2, NULL, thread_func, &arg2);

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    return 0;
}

在上述示例中,我们定义了一个线程函数thread_func,该函数接收一个整数参数,并将其打印出来。在main函数中,我们创建了两个线程,并分别传递不同的参数给它们。通过pthread_create函数,我们将线程函数和参数传递给新创建的线程。然后,我们使用pthread_join函数等待这两个线程执行完成。

pthread_create函数用于创建一个新线程,并将其加入到进程中。该函数需要传入线程ID指针、线程属性、线程函数和参数。成功创建线程后,新线程将从指定的线程函数开始执行,并且可以访问传递给线程函数的参数。在上述示例中,我们可以看到两个线程分别打印了传递给它们的参数,说明它们成功接收到了参数并进行了相应的处理。


在大多数编程语言中,创建线程通常有两种方法:继承Thread类和实现Runnable接口。

继承Thread类

继承Thread类是一种创建线程的简单方法。通过继承Thread类,可以重写run()方法来定义线程的执行逻辑。

以下是一个使用继承Thread类创建线程的示例:

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

// 继承Thread类,重写run()方法
void* thread_func(void* arg) {
    
    
    // 线程的执行逻辑
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    
    
    // 创建线程实例
    pthread_t thread;

    // 启动线程
    pthread_create(&thread, NULL, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread, NULL);

    return 0;
}

在上述示例中,我们通过继承Thread类,重写了run()方法,并在其中定义了线程的执行逻辑。然后使用pthread_create()函数创建了一个线程实例,并通过pthread_join()函数等待线程执行完成。

实现Runnable接口

实现Runnable接口是另一种创建线程的常用方法。通过实现Runnable接口,可以将线程的执行逻辑定义在run()方法中。

以下是一个使用实现Runnable接口创建线程的示例:

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

// 实现Runnable接口,定义run()方法
void* thread_func(void* arg) {
    
    
    // 线程的执行逻辑
    printf("Hello from thread!\n");
    return NULL;
}

int main() {
    
    
    // 创建线程实例
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 创建线程对象
    pthread_create(&thread, &attr, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread, NULL);

    return 0;
}

在上述示例中,我们通过实现Runnable接口,定义了run()方法,并在其中定义了线程的执行逻辑。然后使用pthread_create()函数创建了一个线程实例,并通过pthread_join()函数等待线程执行完成。


四、循环创建多个子线程

步骤

  1. 定义一个线程函数,该函数包含需要在子线程中执行的逻辑。可以在该函数中使用线程参数来区分不同的子线程。

  2. 在主函数中,使用pthread_create函数循环创建多个子线程。每个子线程都调用同一个线程函数。

  3. pthread_create函数中,将线程函数作为线程函数指针传递,并将对应的参数传递给线程函数。可以使用结构体或指针等方式传递参数。

  4. 在线程函数中,根据传递的参数执行相应的逻辑。

示例

下面是一个使用C语言实现的示例代码,演示了如何循环创建多个子线程,每个子线程都继承自同一个线程函数的方法:

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

// 线程函数
void* threadFunc(void* arg) {
    
    
    int thread_num = *(int*)arg;
    printf("Thread %d is running\n", thread_num);
    // 执行线程的逻辑
    // ...
    return NULL;
}

int main() {
    
    
    const int NUM_THREADS = 5;
    pthread_t threads[NUM_THREADS];
    
    for (int i = 0; i < NUM_THREADS; i++) {
    
    
        int* thread_arg = malloc(sizeof(int));
        *thread_arg = i + 1;
        pthread_create(&threads[i], NULL, threadFunc, thread_arg);
    }
    
    for (int i = 0; i < NUM_THREADS; i++) {
    
    
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}

代码解释

在上述示例中,我们定义了一个线程函数threadFunc,其中包含了需要在子线程中执行的逻辑。在主函数中,我们使用pthread_create函数循环创建了5个子线程。每个子线程都调用threadFunc函数,并将对应的参数传递给线程函数。在threadFunc函数中,我们根据传递的参数执行线程的逻辑。


线程间全局变量共享

在多线程编程中,线程间的数据共享是一个常见的问题。线程间可以通过共享内存或全局变量来实现数据的共享。

以下是一个使用全局变量实现线程间数据共享的示例:

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

// 全局变量
int count = 0;

// 实现Runnable接口,定义run()方法
void* thread_func(void* arg) {
    
    
    // 访问和修改全局变量
    count++;
    return NULL;
    int main() {
    
    
    // 创建线程实例
    pthread_t thread1, thread2;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 创建线程对象
    pthread_create(&thread1, &attr, thread_func, NULL);
    pthread_create(&thread2, &attr, thread_func, NULL);

    // 等待线程执行完成
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // 打印全局变量的值
    printf("Count: %d\n", count);

    return 0;
}

在上述示例中,我们使用全局变量count来实现线程间的数据共享。在每个线程中,我们递增count的值。然后在主线程中打印count的值。由于count是全局变量,所有线程都可以访问和修改它,因此最终打印的count的值可能不是预期的结果。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Goforyouqp/article/details/132393065