线程简介
线程是轻量级的进程。操作系统会以进程为单位,分配系统资源。进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。
线程与进程
进程有独立的地址空间,多个线程共用同一个地址空间。
- 线程更加节省系统资源。
- 在一个地址空间中,每个线程都有属于自己的栈区,寄存器。
- 在一个地址空间中,代码段,堆区,全局数据区,文件描述符表都是线程共享的。
线程是程序的最小执行单位,进程是操作系统中最小的资源分配单位。
线程的上下文切换比进程快的多。
线程更加廉价,启动速度更快,退出也快,对系统资源的冲击小。
在处理多任务程序的时候使用多线程比使用多进程要更有优势,但是线程并不是越多越好。
- 处理复杂的算法(主要是CPU进行运算),线程的个数=CPU的核心数。
- 处理IO密集型任务时,因为可以分时复用CPU时间片,所以线程个数可以略大于CPU的核心数(两倍)。
创建线程
线程函数
每个线程都有一个唯一的线程ID,类型为pthread_t,是一个无符号长整形。
1
| pthread_t pthread_self(void);
|
在一个进程中调用线程创建函数,就得到一个子线程,和进程不同,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。
1 2
| #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
|
- thread:传出参数;线程创建成功,会将线程ID写入到这个指针指向的内存中。
- attr:线程的属性,一般情况下使用默认属性,即NULL。
- start_routine:函数指针,创建出的子线程的处理动作,该函数在子线程中执行。
- arg:作为实参传递到start_routine指针指向的函数内部。
- 返回值:创建成功返回0,创建失败返回对应的错误号。
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
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h>
void* callback(void* arg) { for(int i = 0; i < 5; i ++) { printf("子线程: i = %d\n", i); } printf("子线程:%ld\n", pthread_self()); return NULL; }
int main() { pthread_t tid; pthread_create(&tid, NULL, callback, NULL); for(int i = 0; i < 5; i++) { printf("主线程: i = %d\n", i); } printf("主线程: %ld\n", pthread_self()); return 0; }
|
执行结果。子线程还未执行,主线程就执行完毕,将资源释放掉了,所以子线程最终没有执行。
在主线程中加入sleep,等待子线程执行完后,再释放资源。
线程退出
在编写多线程程序的时候,如果想要让线程退出,但是不会导致虚拟地址空间的释放,我们就可以调用线程库中的线程退出函数。
1 2
| #include <pthread.h> void pthread_exit(void *retval);
|
- 参数:线程退出时携带的数据,当前子线程的主线程会得到该数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void* callback(void* arg) { for(int i = 0; i < 5; i ++) { printf("子线程: i = %d\n", i); } printf("子线程:%ld\n", pthread_self()); return NULL; }
int main() { pthread_t tid; pthread_create(&tid, NULL, callback, NULL); printf("主线程: %ld\n", pthread_self()); pthread_exit(NULL); return 0; }
|
主线程结束后资源并未被释放,子线程继续执行完。
线程回收
线程函数
1 2 3 4
| #include <ptread.h>
int pthread_join(pthread_t thread, void **retval);
|
- 参数:
- thread:要被回收的子线程的线程ID
- retval:二级指针,是一个传出参数,这个地址中存储了pthread_exit()传递出的数据,如果不需要,可以指定为NULL。
- 返回值:线程回收成功返回0,回收失败返回错误号。
回收子线程数据
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
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> struct Test{ int num; int age; };
void* callback(void* arg) { for(int i = 0; i < 5; i ++) { printf("子线程: i = %d\n", i); } printf("子线程:%ld\n", pthread_self()); struct Test t; t.num = 100; t.age = 6; pthread_exit(&t); return NULL; }
int main() { pthread_t tid; pthread_create(&tid, NULL, callback, NULL); printf("主线程: %ld\n", pthread_self()); void* ptr; pthread_join(tid, &ptr); struct Test* pt = (struct Test*)ptr; printf("num : %d, age : %d\n", pt->num,pt->age); return 0; }
|
因为t在栈区,所以当子进程结束后, ptr指向的地址空间会被释放,因此最后的输出会是随机数。
将struct Test t定义成全局变量后,num和age正常输出。
或者将t定义在main函数中,并传入到callback内。
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
| void* callback(void* arg) { for(int i = 0; i < 5; i ++) { printf("子线程: i = %d\n", i); } printf("子线程:%ld\n", pthread_self()); struct Test* t = (struct Test*)arg; t->num = 100; t->age = 6; pthread_exit(t); return NULL; }
int main() { pthread_t tid; struct Test t; pthread_create(&tid, NULL, callback, &t); printf("主线程: %ld\n", pthread_self()); void* ptr; pthread_join(tid, &ptr); printf("num : %d, age : %d\n", t.num,t.age); return 0; }
|
线程分离
子线程和主线程分离,当子线程退出时,其占用的内核资源就系统的其他进程接管并回收。
1 2 3
| #include <pthread.h>
int pthread_detach(pthread_t thread);
|
其他函数
线程取消
在某些特定情况下在一个线程中杀死另一个线程,总共有两步:
1.调用pthread_cancel,被指定的线程B不会马上死亡。
2.线程B中进行一次系统调用后,线程B死亡;否则线程B可以一直运行。