• 《Linux/Unix系统编程手册》 第29章 线程:介绍


     关键词:pthread_create()、pthread_exit()、pthread_self()、pthread_join()、pthread_detach()、pthread_attr_init()、pthread_attr_destroy()等等。

    本章首先介绍了线程在进程中内存属性,然后介绍了统一进程中线程间共享属性,以及线程私有属性。

    然后介绍了如何创建线程、终止线程、获取线程ID、连接线程、分离线程,以及如何设置线程属性。

    最后一个应用实现为一组线程还是一组进程进行了对比。

    1. 概述

    一个进程包含多个线程,并均会独立并发执行相同程序共享同一份全局内存区域,包括代码段、初始化数据段、为初始化数据段、堆;但是每个线程都有自己独立的栈

    创建线程相对于创建进程的优点:

    • 进程间信息难以共享。必须通过进程间通信方式,在进程间交换信息。
    • 调用fork()创建进程代价较高。需要复制页表、文件描述符表等多进程属性。

    线程共享的属性还包括:

    进程ID和父进程ID、进程组ID和会话ID、控制终端、进程凭证、打开的文件描述符、fcntl()创建的记录锁、信号处置、文件系统相关信息、setitimer()和timer_create()、systemV信号量、资源限制、CPU时间消耗、资源消耗、nice值。

    线程独有属性包括:

    线程ID、信号掩码、备选信号栈、errno变量、浮点型环境、实时调度策略和优先级、CPU亲和力、能力、栈、本地变量和函数调用链接。

    2. Pthreads API的详细背景

    线程数据类型:

     Linux多线程环境中,每个线程都有自己的errno

    Pthreads函数返回值,0表示成功,正值表示失败

    调用Pthreads API进行编程时,需要-lpthread进行链接。

    3. 创建线程

    函数pthread_create()负责创建新的线程:

    #include <pthread.h>
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
    void *(*start)(void *), void *arg);
        Returns 0 on success, or a positive error number on error

    新线程通过调用带有参数arg的函数start而开始执行。

    arg为void*类型,可以执行一个全局变量或堆变量、或NULL、或一个结构体指针。

    start()返回值类型为vod*。

    thread指向pthread_t类型的指针,后续Pthreads函数将使用该标识来引用该线程。

    attr指向pthread_attr_t对象的指针,该对象定义了新线程的各种属性。

    线程创建后,应用程序无法确定系统会接着调度哪一个线程来执行

    4. 终止线程

    终止线程的方式:

    • 线程函数中执行return语句并返回指定值。
    • 线程调用pthread_exit()。
    • 调用pthread_cancel()取消线程。
    • 任意线程调用了exit(),或主线程执行了return语句,都会导致进程中所有线程立即终止。

    pthread_exit()将终止调用线程,且返回值可由另一线程通过调用pthread_join()来获取:

    include <pthread.h>
    void pthread_exit(void *retval);

    在线程函数调用的任意函数中调用pthread_exit()则终止当前线程。

    retval指定了线程的返回值。retval所指向的内容不应分配于线程栈中。

    如果主线程调用了pthread_exit(),而非exit()或return语句,那其他线程将继续执行。

    5. 线程ID

    线程可以通过pthread_self()来获取自己的线程ID:

    include <pthread.h>
    pthread_t pthread_self(void);
        Returns the thread ID of the calling thread

    获取线程ID有以下作用:

    • pthread_join()、pthread_detach()、pthread_cancel()、pthread_kill需要线程ID来操作目标线程。
    • 以特定线程ID作为动态数据结构的标签。

    pthread_equal()可检查两个线程的ID是否相同:

    include <pthread.h>
    int pthread_equal(pthread_t t1, pthread_t t2);
        Returns nonzero value if t1 and t2 are equal, otherwise 0

    6. 连接已终止的线程

    pthread_join()等待由thread标识的线程终止:

    include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);
        Returns 0 on success, or a positive error number on error

    retval为一非空指针,保存线程终止时返回值的拷贝,该返回值亦即线程调用return或pthread_exit()时所制定的值。

    如果pthread_join()传入一个之前已然连接过的线程ID,将导致无法预知的行为。

    若线程未分离,则必须使用pthread_join()来进行连接。如未能连接,则线程终止时将产生僵尸线程。

    pthread_join()和waitpid()区别:

    • 线程之间的关系是对等的。进程中任意线程均可以调用pthread_join()与该进程中任何其他线程链接起来。
    • pthread_join()无法一次连接任意线程,也不能以非阻塞方式进行阻塞。使用条件变量可以实现类似的功能。waitpid(-1,&status, options)可以一次连接任意进程。
    #include <pthread.h>
    #include "tlpi_hdr.h"
    
    static void *
    threadFunc(void *arg)
    {
        char *s = (char *) arg;
    
        printf("%s", s);
    
        return (void *) strlen(s);
    }
    
    int
    main(int argc, char *argv[])
    {
        pthread_t t1;
        void *res;
        int s;
    
        s = pthread_create(&t1, NULL, threadFunc, "Hello world
    ");
        if (s != 0)
            errExitEN(s, "pthread_create");
    
        printf("Message from main()
    ");
        s = pthread_join(t1, &res);
        if (s != 0)
            errExitEN(s, "pthread_join");
    
        printf("Thread returned %ld
    ", (long) res);
    
        exit(EXIT_SUCCESS);
    }

    7. 线程的分离

    pthread_detach()将thread指定的线程标记为处于分离状态:

    #include <pthread.h>
    int pthread_detach(pthread_t thread);
        Returns 0 on success, or a positive error number on error

    pthread_detach(pthread_self())线程可以自行分离

    一旦线程处于分离状态,就不能再用pthread_join()获取状态,也无法使其返回可连接状态

    其他线程调用了exit()或主线程return,即便遭分离的线程还是会受影响,所有线程会立即终止

    8. 线程属性

    pthread_attr_init()对线程属性结构进行初始化,pthread_create()创建线程的时候使用,使用完后通过pthread_attr_destroy()进行销毁。

    9. 线程VS进程

    实际应用实现为一组线程还是进程,需要对比线程和进程。

    首先看线程优点:

    • 线程间的数据共享简单
    • 创建线程要快于创建进程

    线程缺点如下:

    • 多线程编程时,需要确保调用线程安全的函数
    • 某个线程bug可能会危及该进程所有线程,因为他们共享相同地址空间以及其他属性。
    • 每个线程都争用宿主进程中有限虚拟地址空间。每个线程栈和线程特有数据都小号进程虚拟地址空间一部分。

    其他影响点包括:

    • 多线程中处理信号,需要小心设计。多线程中避免使用信号
    • 多线程应用中,所有线程必须运行同一个程序。多进程应用,不同进程可以运行不同程序。
    • 除了数据,线程还可以共享某些其他信息。
  • 相关阅读:
    SetWindowsHookEx详解
    C#使用全局钩子(hook),SetWindowsHookEx返回0、不回调的解决
    C#使用全局钩子(hook),SetWindowsHookEx返回0、不回调的解决
    how to get geometry type of layer using IMapServer3 and IMapLayerInfo? (C#)
    how to get geometry type of layer using IMapServer3 and IMapLayerInfo? (C#)
    windows cmd命令显示UTF8设置
    windows cmd命令显示UTF8设置
    C#写的NoSQL开源项目/系统(系列)
    TCP协议详解(2)
    红黑树
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/9776269.html
Copyright © 2020-2023  润新知