线程控制原语 | 进程控制原语 | |
创建 | pthread_create() | fork(); |
得到id | pthread_self() | getpid(); |
退出 | pthread_exit() | exit(); / return |
回收 | pthread_join() | wait()/waitpid() |
杀死 | pthread_cancel() | kill() |
线程分离 | pthread_detach() |
创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
线程之间共享和非共享资源
共享资源
-
进程 ID 和父进程 ID
-
进程组 ID 和会话 ID
-
用户 ID 和 用户组 ID
-
文件描述符表
-
信号处置
-
文件系统的相关信息:文件权限掩码(
umask
)、当前工作目录 -
虚拟地址空间(除栈、.text)
非共享资源
-
线程 ID
-
信号掩码
-
线程特有数据
-
error 变量
-
实时调度策略和优先级
-
栈,本地变量和函数的调用链接信息
.text
代码区中主要存放程序中的代码(二进制),属性是只读。
每个线程执行的代码可能不一样。每个线程的局部变量是不一样的;但,所有的线程都共享全局变量。
栈区(stack)
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
vs运行效果如下
pthread_create 函数
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。
程序中默认只有一个进程,fork()函数调用,2个进程。
程序中默认只有一个线程,pthread_create()函数调用,2个线程。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_t:typedef unsigned long int pthread_t;
void *(*start_routine) (void *) 的理解:参数是 void *,返回值也是 void * 的回调函数。
- 功能:创建一个子线程
- 参数:
- thread:传出参数,线程创建成功后,子线程的线程ID 通过指针 被写到该变量中。
- attr : 设置线程的属性,一般使用默认值,NULL
- start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码。该函数运行结束,则线程结束。
- arg : 给第三个参数使用,传参。传参是系统帮我们自动调用。
- 返回值:
成功:0
失败:返回错误号。这个错误号和之前errno不太一样。Linux 环境下,所有线程特点,失败均直接返回错误号。
获取错误号的信息: char * strerror(int errnum);
代码:
void* tfn(void *arg){
printf("pthread: pid = %d, tid = %lu \n", getpid(), pthread_self());
return NULL;
}
// 主线程
int main(int argc, char* argv[])
{
pthread_t tid; // pthread_t --> unsigned long int
printf("main: pid = %d, tid = %lu \n", getpid(), pthread_self());
//创建一个子线程。tid 传出参数,传出的是子线程id。
int ret = pthread_create(&tid, NULL, tfn, NULL);
printf("tid = %lu \n", tid); // tid 是子线程id
sleep(2);
return 0;
}
这里main函数是主线程,tfn是子线程,pthread_create()是用来创建子线程。程序默认只有一个进程,进程pid=4495。主线程和子线程都是属于这个进程,但线程tid不同。
主线程与子线程之间的关系
主线程中开启了一个子线程,开启之后,主线程与子线程互不影响各自的生命周期,即主线程结束,子线程还可以继续执行;子线程结束,主线程也能继续执行。
对于程序来说,如果主进程在子进程还未结束时就已经退出,那么Linux内核会将子进程的父进程ID改为1(也就是init进程),当子进程结束后会由init进程来回收该子进程。
那如果是把进程换成线程的话,会怎么样呢?假设主线程在子线程结束前就已经退出,子线程会发生什么?
在一些论坛上看到许多人说子线程也会跟着退出,其实这是错误的,原因在于他们混淆了线程退出和进程退出概念。实际的答案是主线程退出后子线程的状态依赖于它所在的进程,如果进程没有退出的话子线程依然正常运转。如果进程退出了,那么它所有的线程都会退出,所以子线程也就退出了。
/*
#include <pthread.h>
void pthread_exit(void *retval);
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程
参数:
retval:需要传递一个指针,作为一个返回值,可以在pthread_join()中获取到。
pthread_t pthread_self(void);
功能:获取当前的线程的线程ID
int pthread_equal(pthread_t t1, pthread_t t2);
功能:比较两个线程ID是否相等
不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的
是使用结构体去实现的。
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
void * callback(void * arg) {
printf("child thread id : %ld\n", pthread_self());
return NULL; // pthread_exit(NULL);
}
int main() {
// 创建一个子线程
pthread_t tid;
int ret = pthread_create(&tid, NULL, callback, NULL);
// 主线程
for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}
printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());
// 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
pthread_exit(NULL);
printf("main thread exit\n");
// 退出进程,但由于主线程已经退出,这行代码不会执行。
return 0; // exit(0);
}
pthread_exit(NULL),是用来终止线程的,可以终止主线程和子线程。主线程结束是不影响子线程运行的。
exit(0),用来退出进程。如果进程退出了,那么它所有的线程都会退出,所以子线程也就退出了。
主函数中的 return 0;相当于exit(0);
子函数中的 return NULL;相当于 pthread_exit(NULL);
pthread_join 函数
阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid() 函数。
int pthread_join(pthread_t thread, void **retval);
返回值:成功:0;失败:错误号
参数:thread:线程 ID (【注意】:不是指针);retval:存储线程结束状态。
对比记忆:
进程中:main 返回值、exit 参数-->int;等待子进程结束 wait 函数参数-->int *
线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **
struct pthread {
int val;
char str[256];
};
void *tfn(void *arg){
struct pthread *t1;
t1 = malloc(sizeof(struct pthread)); //线程退出了,线程函数内的栈(局部变量)会被回收,不用堆空间(malloc)会出错
t1->val = 100;
strcpy(t1->str, "hello pthread!");
return (void*)t1;
}
int main(int argc, char* argv[])
{
pthread_t tid;
struct pthread *t;
int ret = pthread_create(&tid, NULL, tfn, NULL);
// int pthread_join(pthread_t thread, void **retval);
pthread_join(tid, (void**)&t); // waitpid,指定线程回收,并接受子线程返回状态。
printf("child thread exit with var = %d, str = %s\n", t->val, t->str);
// return 0;
pthread_exit(NULL); //pthread_exit() 函数只会终止当前线程,不会影响进程中其它线程的执行。
}
struct pthread {
int val;
char str[256];
};
// void的字面意思是“无类型”,void *则为“无类型指针”
void *tfn(void *arg){
struct pthread *t1;
t1 = (struct pthread *)arg;
t1->val = 100;
strcpy(t1->str, "hello pthread!");
return (void*)t1;
}
int main(int argc, char* argv[])
{
pthread_t tid;
struct pthread arg;
struct pthread *t;
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
int ret = pthread_create(&tid, NULL, tfn, (void *) &arg);
// int pthread_join(pthread_t thread, void **retval);
pthread_join(tid, (void**)&t); // waitpid,指定线程回收,并接受子线程返回状态。
printf("child thread exit with var = %d, str = %s\n", arg.val, arg.str);
printf("child thread exit with var = %d, str = %s\n", t->val, t->str);
// return 0;
pthread_exit(NULL); //pthread_exit() 函数只会终止当前线程,不会影响进程中其它线程的执行。
}