LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

《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\n");
    if (s != 0)
        errExitEN(s, "pthread_create");

    printf("Message from main()\n");
    s = pthread_join(t1, &res);
    if (s != 0)
        errExitEN(s, "pthread_join");

    printf("Thread returned %ld\n", (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可能会危及该进程所有线程,因为他们共享相同地址空间以及其他属性。
  • 每个线程都争用宿主进程中有限虚拟地址空间。每个线程栈和线程特有数据都小号进程虚拟地址空间一部分。

其他影响点包括:

  • 多线程中处理信号,需要小心设计。多线程中避免使用信号
  • 多线程应用中,所有线程必须运行同一个程序。多进程应用,不同进程可以运行不同程序。
  • 除了数据,线程还可以共享某些其他信息。

posted on 2020-12-20 22:01  ArnoldLu  阅读(489)  评论(0编辑  收藏  举报

导航