文章目录

  • 一、线程概述
  • 二、线程控制
  • 1、线程标识
  • (1)获取线程号
  • (2)线程号的比较
  • 2、线程创建
  • 3、线程终止
  • (1)线程退出
  • (2)线程取消
  • (3)线程回收(阻塞)
  • (4)线程回收(非阻塞)
  • 三、线程私有数据
  • 1、创建线程私有数据
  • 2、注销线程私有数据
  • 3、设置线程私有数据的关联
  • 4、读取线程私有数据所关联的值


一、线程概述

在许多经典的操作系统教科书中,总是把进程定义为程序的执行实例,它并不执行什么, 只是维护应用程序所需的各种资源,而线程则是真正的执行实体。在一个进程中的多个执行路线叫做线程。为了让进程完成一定的工作,进程必须至少包含一个线程。

  线程又叫轻量级进程(LWP)。

线程概述、线程控制和线程私有数据_线程概述

线程会共享进程的一些资源,也有一些资源是线程独立拥有的。但是不同进程的线程是不共享资源的

【共享】:
  代码区、数据区、堆区(注意没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户 ID 和组 ID 等。

【非共享】:
   ID、寄存器值、栈内存、调度策略和优先级、信号掩码、errno变量以及线程私有数据等。

也可以说线程是包含在进程中的一种实体。它有自己的运行线索,可完成特定任务。可与其他线程共享进程中的共享变量及部分环境。可通过相互之间协同来完成进程所要完成的任务

二、线程控制

1、线程标识

就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。进程号用 pid_t 数据类型表示,是一个非负整数。线程号则用 pthread_t 数据类型来表示,Linux 使用无符号长整数表示。有的系统在实现 pthread_t 的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理。

(1)获取线程号

#include <pthread.h>
pthread_t pthread_self(void);
  • 功能: 获取线程号。
  • 参数: 无。
  • 返回值: 调用线程的线程 ID 。

(2)线程号的比较

#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
  • 功能: 判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
  • 参数: t1,t2:待判断的线程号。
  • 返回值: 相等:非 0;不相等:0。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int main()
{
    pthread_t thread_id;
    thread_id = pthread_self(); // 获取线程号
    printf("thread id = %lu\n", thread_id);
    if (pthread_equal(thread_id, pthread_self())) // 线程号比较
        printf("Equal\n");
    else
        printf("not Equal\n");
    return 0;
}
// 线程函数的程序在 pthread 库中,故链接时要加上参数 -lpthread。

2、线程创建

#include <pthread.h>
int pthread_create( pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg );
  • 功能: 创建一个线程。
  • 参数:
  • thread:线程标识符地址;
  • attr:线程属性结构体地址,通常设置为 NULL;
  • start_routine:线程函数的入口地址;
  • arg:传给线程函数的参数。
  • 返回值: 成功:0;失败:非0。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

void *fun(void *arg)
{
    int *p = (int *)arg;
    printf("child thread pid = %d\n", getpid());
    printf("child thread id = %lu\n", pthread_self());
    printf("child thread *arg = %d\n", *p);
    sleep(1);
}

int main()
{
    pthread_t tid;
    int n = 10;
    // 创建一个线程,fun是线程函数,n是传给线程函数的参数,
    // 强制转换为void *类型。
    int err = pthread_create(&tid, NULL, fun, (void *)&n);
    if (err != 0)
    {
    	// 如果创建失败,打印出错信息。
        fprintf(stderr, "can't create thread:%s\n", strerror(err)); 
        exit(1);
    }
    printf("main pid = %d\n", getpid());
    printf("main thread id = %lu\n", pthread_self());
    printf("main child thread id = %lu\n", tid);
    // 线程创建时并不能保证哪个线程先运行:是新建的线程还是调用线程。
    sleep(2); 
    return 0;
}

3、线程终止

(1)线程退出

#include <pthread.h>
void pthread_exit(void *retval);
  • 功能: 主要用于终止正在运行的线程,但并不释放资源。
  • 参数:
  • retval:来带出线程的退出状态信息。
  • 返回值: 无。

在线程过程函数或者被线程过程函数直接或间接调用的函数中,调用 pthread_exit 函数,其效果都与在线程过程函数中执行 return 语句效果一样 – 终止调用线程。注意,在任何线程中调用 exit 函数,被终止的都是进程。当然随着进程的终止,隶属于该进程的包括调用线程在内的所有线程也都一并终止。

【Note】:
  如果thread线程函数从return返回,则retval存放的是thread线程函数的返回值;如果从pthread_exit返回,则retval存放的是pthread_exit的参数。

(2)线程取消

#include <pthread.h>
int pthread_cancel(pthread_t thread);
  • 功能: 对参数指定的线程发送取消的请求(必须是同一进程中的其他线程)。
  • 参数:
  • thread:线程号。
  • 返回值: 成功:0;失败:错误码。

该函数只是向线程发出取消请求,并不等于线程终止。缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。在取消点处,线程检查其自身是否已被取消,若是则立即终止。当线程调用一些特定函数时,取消点会出现。

(3)线程回收(阻塞)

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
  • 功能: 等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
  • 参数:
  • thread:被等待的线程号;
  • retval:用来存储线程退出状态的指针的地址。
  • 返回值: 成功:0;失败:非0。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *thrfun1(void *p)
{
    int sum = 0;
    for (int i = 1; i <= 10; ++i)
        sum += i;
    printf("子线程1退出\n");
    return (void *)sum; // 为了保持类型匹配,强制转换。
}

void *thrfun2(void *p)
{
    printf("子线程2退出\n");
    pthread_exit((void *)2); // 本线程退出,不影响其他线程。
}

void *thrfun3(void *p)
{
    while (1)
    {
        printf("子线程3运行\n");
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    void *it;
    pthread_create(&tid, NULL, thrfun1, NULL);
    pthread_join(tid, &it);
    printf("子线程1返回的数据是:%ld\n", (long)it);
    
    pthread_create(&tid, NULL, thrfun2, NULL);
    pthread_join(tid, &it);
    printf("子线程2返回的数据是:%ld\n", (long)it);
    
    pthread_create(&tid, NULL, thrfun3, NULL);
    sleep(3); // 主控线程运行三秒后取消子线程3
    pthread_cancel(tid); // 线程被取消
    // 回收由pthread_cancel终止的进程,返回值是一个宏(-1)
    pthread_join(tid, &it); 
    printf("子线程3返回的数据是:%ld\n", (long)it);
    
    return 0;
}

(4)线程回收(非阻塞)

#include <pthread.h>
int pthread_detach(pthread_t thread);
  • 功能: 主要用于将参数指定的线程标记为分离状态。
  • 参数:
  • thread:线程号;
  • 返回值: 成功:0;失败:错误码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

void *thrfun(void *p)
{
    int n = 3;
    while (n--)
    {
        printf("thread count %d\n", n);
        sleep(1);
    }
    return (void *)1;
}

int main()
{
    pthread_t tid;
    void *it;
    int err;
    pthread_create(&tid, NULL, thrfun, NULL);
    // 如果没有pthread_detach,主控线程会阻塞回收子线程,
    // 并且打印返回状态和出错码。
    // 如果设置了pthread_detach,主控线程非阻塞回收子线程,
    // 这种情况下再调用pthread_join会出错。
    pthread_detach(tid);
    while (1)
    {
        err = pthread_join(tid, &it);
        if (err != 0)
            fprintf(stderr, "thread %s\n", strerror(err));
        else
            fprintf(stderr, "thread exit code %ld\n", (long)it);
        sleep(1);
    }
    return 0;
}

对于分离状态的线程来说:当该线程终止后,会自动将资源释放给系统,不需要其他线程的加入/等待(即:立即回收资源),也就是说分离的线程无法被其他线程使用 pthread_join 进行等待。建议:对于新启动的线程来说,要么使用 pthread_detach 设置为分离状态,要么使用 pthread_join 设置为可加状态(即:pthread_join 和 pthread_detach 是互斥的)。

【Note】:

在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

三、线程私有数据

有时应用程序设计中必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。比如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由 Posix 线程库维护,成为线程私有数据 (Thread-specific Data,或称为TSD)。

1、创建线程私有数据

#include <pthread.h> 
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
  • 功能: 创建一个类型为 pthread_key_t 类型的私有数据变量( key )。
  • 参数:
  • key:在分配( malloc )线程私有数据之前,需要创建和线程私有数据相关联的键( key ),这个键的功能是获得对线程私有数据的访问权;
  • destructor:清理函数名字( 如:fun )。当线程退出时,如果线程私有数据地址不是非 NULL,此函数会自动被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。
  • 返回值: 成功:0;失败:非0。

不论哪个线程调用 pthread_key_create(),所创建的 key 都是所有线程可访问,但各个线程可根据自己的需要往 key 中填入不同的值,相当于提供了一个同名不同值的变量。

2、注销线程私有数据

#include <pthread.h> 
int pthread_key_delete(pthread_key_t key);
  • 功能: 注销线程私有数据。这个函数并不会检查当前是否有线程正使用线程私有数据( key ),也不会调用清理函数 destructor() ,而只是将线程私有数据( key )释放以供下一次调用 pthread_key_create() 使用。
  • 参数:
  • key:待注销的私有数据。
  • 返回值: 成功:0;失败:非0。

3、设置线程私有数据的关联

#include <pthread.h> 
int pthread_setspecific(pthread_key_t key, const void *value);
  • 功能: 设置线程私有数据( key ) 和 value 关联,注意,是 value 的值(不是所指的内容)和 key 相关联。
  • 参数:
  • key:线程私有数据;
  • value:和 key 相关联的指针。
  • 返回值: 成功:0;失败:非0。

4、读取线程私有数据所关联的值

#include <pthread.h> 
void *pthread_getspecific(pthread_key_t key);
  • 功能: 读取线程私有数据( key )所关联的值。
  • 参数:
  • key:线程私有数据;
  • 返回值: 成功:线程私有数据( key )所关联的值;失败:NULL。
#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h>

pthread_key_t key;  // 私有数据,全局变量
 
void echomsg(void *t) 
{ 
    printf("[destructor] thread_id = %lu, param = %p\n", pthread_self(), t); 
} 
 
void *child1(void *arg) 
{ 
    int i = 10;
    
    pthread_t tid = pthread_self(); //线程号
    printf("\nset key value %d in thread %lu\n", i, tid); 
    
    pthread_setspecific(key, &i); // 设置私有数据
    
    printf("thread one sleep 2 until thread two finish\n\n");
    sleep(2); 
    printf("\nthread %lu returns %d, add is %p\n", tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key)); 
} 
 
void *child2(void *arg) 
{ 
    int temp = 20;
    
    pthread_t tid = pthread_self();  //线程号
    printf("\nset key value %d in thread %lu\n", temp, tid); 
    
    pthread_setspecific(key, &temp); //设置私有数据
    
    sleep(1); 
    printf("thread %lu returns %d, add is %p\n", tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key)); 
} 
 
int main(void) 
{ 
    pthread_t tid1,tid2; 
    pthread_key_create(&key, echomsg); // 创建
    
    pthread_create(&tid1, NULL, child1, NULL); 
    pthread_create(&tid2, NULL, child2, NULL); 
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    
    pthread_key_delete(key); // 注销
    
    return 0; 
}