文章目录
- 一、线程概述
- 二、线程控制
- 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;
}