讲解 Linux 系统中的线程的使用

本文主要用于记录所学所用!

背景知识

Linux 没有真正意义上的线程,它的实现是由进程来模拟,所以属于用户级线程,位于 libpthread 共享库(所以线程的 ID 只在库中有效),遵循 POSIX 标准。

历史上,Linux 有三种线程实现。分别是 LinuxThreads,NGPT(Next Generation POSIX Threads),NPTL(Native POSIX Threads Library)。

  • LinuxThreads:受限于内核特性,违背了 SUSV3 Pthreads标准。
  • NGPT: 由 ibm 开发,曾被作为 LinuxThreads 的继任者。
  • NPTL: 由于性能由于 NGPT, 因而目前使用的就是 NPTL。

有关 Linux 线程进一步介绍请看Linux历史上线程的3种实现模型

Windows 下有一个真正的数据结构 TCB 来描述线程。

进程与线程的区别

  • 进程:程序的一个动态运行实例,承担分配系统资源的实例。( Linux 实现进程的主要目的是资源独占)

  • 线程:在进程的内部运行(进程的地址空间)的一个分支,也是调度的基本单位(调度按 LWP 调度)。线程的所有资源由进程提供。( Linux 实现线程的主要目的是资源共享)

进程与线程共享内容

由于同一进程的多个线程共享同一地址空间,因 此 Text Segment、Data Segment 都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境。

  1. 文件描述符表
  2. 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
  3. 当前工作目录
  4. 用户id和组id

进程与线程非共享部分

  1. 线程ID
  2. 上下文信息,包括各种寄存器的值、程序计数器和栈指针
  3. 栈空间
  4. errno变量
  5. 信号屏蔽字
  6. 调度优先级

线程的介绍

在 Linux 系统中,与线程相关的函数都定义在 pthread.h 中。其中线程主要涉及以下几个主要函数,分别是pthread_create()pthread_cancel()pthread_exit()pthread_join()。下面会对函数进行分别的讲解。

pthread_create()

此函数主要用于线程的创建,其定义如下:

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

下面分别解释这些参数的含义:

  • thread 参数是新线程的标识符,为一个整型。

  • attr 参数用于设置新线程的属性。给传递 NULL 表示设置为默认线程属性。

  • start_routine 和 arg 参数分别指定新线程将运行的函数和参数。start_routine 返回时,这个线程就退出了

  • 返回值:成功返回0,失败返回错误号。

    • 线程id的类型是 thread_t ,它只在当前进程中保证是唯一的,在不同的系统中 thread_t 这个类型有不同的实现,调用pthread_self()可以获得当前线程的 id

    • 进程 id 的类型时 pid_t ,每个进程的 id 在整个系统中是唯一的,调用getpid()可以获得当前进程的 id ,是一个正整数值。

pthread_cancel()pthread_exit()

终止某个线程而不终止整个进程。可以有三种方法:

  • 从线程函数 return。这种方法对主线程不适应,从 main 函数return 相当于调用 exit。

    1
    2
    3
    4
    5
    void* thread(void *arg) {
    printf("this is thread and arg = %d\n", *(int*)arg);
    printf("this is a = %d\n", a++);
    return arg;
    }
  • 一个线程可以调用 pthread_cancel终止同一进程中的另一个线程。此函数的定义如下所示:

    1
    2
    3

    int pthread_cancel(pthread_t thread);

    其中 thread 参数是目标贤臣轨道标识符。该函数成功会返回 0, 错误会返回错误码。

  • 线程可以调用pthread_exit终止自己

    1
    2
    3

    void pthread_exit(void *retval);

    1. retval 是 void * 类型,其它线程可以调用 pthread_join() 获得这个指针。

      注意, pthread_exit() 或者 return 返回的指针所指向的内存单元必须是全局的或者是由malloc()分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

    2. pthread_exit() 函数通过retval 参数向线程的回收者传递其退出信息。它执行之后不会返回到调用者,且永远不会失败。

pthread_join()

此函数用于等待线程终止(包括正常与非正常),其函数定义如下所示:

1
2
3

int pthread_join(pthread_t thread, void **retval);

其参数的含义如下:

  • thread:需要等待结束线程的 ID。
  • retval:线程终止后的返回值。这个值在 return 终止线程情况下为 return 的返回值;在 pthread_cancel()情况下,值为 PTHREAD_CANCELED;在pthread_exit()情况下,为传递参数的值。

其返回值可能有如下所示内容:

错误码 描述
EDEADLK 可能引起死锁。比如两个线程互相针对对方调用pthread_join,或者线程自身嗲用pthread_join
EINVAL 目标线程是不可回收的的(分离线程)或者已有其他线程在回收该目标前程
ESRCH 目标线程不存在

线程的简单使用

以下是线程的简单使用例子程序,主要在 linux 平台运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<stdio.h>
#include<pthread.h>

int a = 0;

void* thread(void *arg) {

printf("this is thread and arg = %d\n", *(int*)arg);

printf("this is a = %d\n", a++);

*(int*)arg = 0;

return arg;
}

int main(int argc, char* argv[]) {

pthread_t t1, t2;

int ret = 0;

int arg = 10;

int *thread_ret = NULL;

ret = pthread_create(&t1, NULL, thread, &arg);

ret = pthread_create(&t2, NULL, thread, &arg);

if(ret != 0) {

printf("create thread error.\n");

return -1;
}

printf("this is main process.\n");

pthread_join(t1, (void**)&thread_ret);

pthread_join(t2, (void**)&thread_ret);

printf("thread_ret = %d.\n", *thread_ret);

return 0;
}

分离线程

  1. 在任何一个时间点上,线程是可结合的( joinable )或者是分离的( detached )。

  2. 一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源
    (例如栈)是不释放的。(默认情况下线程的创建都是可结合的)

  3. 一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终止时由系统自动释放。

  4. 如果一个可结合线程结束运行但没有被 join,会导致部分资源没有被回收,所以创建线程者应该调用pthread_join()来等待线程运行结束,并可得到线程的退出代码,回收其资源。

调用pthread_join()后,如果该线程没有运行结束,调用者会被阻塞。如何解决这种情况呢?

例如,在 Web 服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join()而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码 pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为分离的( detached ),如此一来,该线程运行结束后会自动释放所有资源。验证代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<error.h>

void* thread(void* arg) {

pthread_detach(pthread_self());

printf( "%s\n", ( char*)arg);

return NULL;
}

int main(int argc, char* argv[]) {

pthread_t pid;

int ret = pthread_create(&pid, NULL, thread, "thread run.....");

if (ret == 0) {

sleep(1);

int tret = pthread_join(pid, NULL);

if (tret == 0) {

printf("pthread join success!");

return tret;
}
else {
printf( "pthread_join failed info\n");

return tret;
}
}
else
{
printf( "create pthread failed info: %s", strerror(ret));
return ret;
}
}

引用

Linux多线程编程之pthread

Linux历史上线程的3种实现模型