一.线程概念
线程概念:
进程:有独立的 进程地址空间。有独立的pcb。
进程是分配资源的最小单位。
线程:没有独立的进程地址空间。有独立的pcb。
进程是cup执行的最小单位。
线程有线程id和线程号,线程id是用于进程内部标识线程的属性,而线程号是用于cpu标识执行单位的属性。
-
-Linux内核 实现线程的原理如下
-
线程之间的共享与非共享关系
线程共享:
独享 栈空间(内核栈、用户栈),每个线程有独立的栈帧
共享 全局区,代码区,堆区 ./text. /data ./rodata ./bsss heap —> 共享【全局变量】
二. 三级映射(三级页表机制)
进程地址空间内部在经过MMU将数据映射到内存时,要经过三级页表查询逻辑地址映射到的实际物理地址,三级页表存在于进程的pcb中,对于线程来说,虽然pcb各自独立,但pcb中的三级页表是相同的。
三.线程操作函数
线程id类型: pthread_t
1.pthread_t pthread_self( ); 获取线程id。
返回值:线程id
出错返回:错误的error码。可以用 printf(strerror(ret))打印错误信息;
2.int pthread_create(pthread_t* tid, const pthread_attr_t* attr, 回调函数, void* arg);创建子线程。
参1:传出参数,表新创建的子线程 id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。void* (void*) 返回值和参数都是void* 类型
参4:参3的参数。没有的话,传NULL
返回值:成功:0 失败:errno
3.void pthread_exit(void* retval); 退出当前线程。
retval:退出值。 无退出值时,NULL
该函数与进程退出函数exit()类似,目的是退出线程。
将其与其它函数进行对比:
exit(); 退出当前进程。
return: 返回到调用者那里去,结束当前函数。
pthread_exit(): 退出当前线程。
由于主线程可能先于子线程结束,所以子线程的可能在主线程结束前不能完全执行,可以用主线程sleep等待子线程结束来解决,而现在就可以使用pthread_exit来解决了。方法就是将return 0替换为pthread_exit,只退出当先线程,不会对其他线程造成影响。
4.int pthread_join(pthread_t thread, void** retval) 阻塞 回收线程。
thread: 待回收的线程id
retval:传出参数。 回收的那个线程的退出值。
线程异常借助,值为 -1。
返回值:成功:0 失败:errno
回收线程并获取其返回值的例子:
1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <string.h>
4.#include <unistd.h>
5.#include <errno.h>
6.#include <pthread.h>
7. //子线程返回此结构体
8.struct thrd {
8. int var;
9. char str[256];
11.};
10. //错误处理
13.void sys_err(const char *str)
14.{
11. perror(str);
12. exit(1);
17.}
13. //线程执行函数
19.void *tfn(void *arg)
20.{
14. struct thrd *tval;
15. //利用指针堆区创建,由于线程间共享堆区,所以主线程可以通过该指针访
16. //问结构体内容,反观创建在栈上,若子线程返回后结束,栈区连同该结构体
17. //一起释放,主线程就无法访问。
18. tval = malloc(sizeof(tval));
19. tval->var = 100;
20. strcpy(tval->str, "hello thread");
21. //子线程将此结构体地址转为void*返回
22. return (void *)tval;
28.}
23.
30.int main(int argc, char *argv[])
31.{
24. pthread_t tid;
25. struct thrd *retval;
26.
27. int ret = pthread_create(&tid, NULL, tfn, NULL);
28. if (ret != 0)
29. sys_err("pthread_create error");
30.
31. //int pthread_join(pthread_t thread, void **retval);
32. ret = pthread_join(tid, (void **)&retval);
33. if (ret != 0)
34. sys_err("pthread_join error");
35.
36. printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);
37.
38. pthread_exit(NULL);
39.
49.}
结果:
5.int pthread_cancel(pthread_t thread); 杀死一个线程。 (需要到达取消点(保存点))
取消点:相当于进入内核的一个契机,当子线程逻辑中有某些操作需要进入内核时,检查是否取消该线程。
参数:
thread: 待杀死的线程id
返回值:成功:0 失败:errno
如果,子线程没有到达取消点, 那么 pthread_cancel 无效。
我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();
成功被 pthread_cancel() 杀死的线程,返回(void*) -1. 可以使用pthead_join 回收。
借助cancel函数杀死线程例子:
1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <string.h>
4.#include <unistd.h>
5.#include <errno.h>
6.#include <pthread.h>
7.
8.
9.void *tfn(void *arg){
10. while (1) {
11. printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
12. sleep(1);
13. }
14.
15. return NULL;
16.}
17.
18.int main(int argc, char *argv[]){
19. pthread_t tid;
20.
21. int ret = pthread_create(&tid, NULL, tfn, NULL);
22. if (ret != 0) {
23. fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
24. exit(1);
25. }
26.
27. printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
28. //5秒后执行cancel函数,杀死线程
29. sleep(5);
30.
31. ret = pthread_cancel(tid); // 以tid(线程id)终止线程
32. if (ret != 0) {
33. fprintf(stderr, "pthread_cancel error:%s\n", strerror(ret));
34. exit(1);
35. }
36.
37.
38. pthread_exit((void *)0);
39.}
结果
注意一点,pthread_cancel工作的必要条件是进入内核,如果tfn真的奇葩到没有进入内核,则pthread_cancel不能杀死线程,此时需要手动设置取消点,就是pthread_testcancel()
6.int pthread_detach(pthread_t thread); 设置线程分离
线程分离,指线程执行完毕后无需其他线程使用join回收,而是会将自身回收。
thread: 待分离的线程id
返回值:成功:0 失败:errno
1.#include <stdio.h>
2.#include <stdlib.h>
3.#include <string.h>
4.#include <unistd.h>
5.#include <errno.h>
6.#include <pthread.h>
7.
8.
9.void *tfn(void *arg)
10.{
9. printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());
10.
11. return NULL;
14.}
12.
16.int main(int argc, char *argv[])
17.{
13. pthread_t tid;
14. // 线程创建
15. int ret = pthread_create(&tid, NULL, tfn, NULL);
16. if (ret != 0) {
17. fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
18. exit(1);
19. }
20.
21. // 设置线程分离 线程终止,会自动清理pcb,无需回收
22. ret = pthread_detach(tid);
23. if (ret != 0) {
24. fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
25. exit(1);
26. }
27 //等待子线程结束后,再使用join回收,测试结果
28. sleep(1);
29.
30. ret = pthread_join(tid, NULL); //此时线程已经自动回收PCB,此函数会出错
31. printf("join ret = %d\n", ret);
32. if (ret != 0) {
33. fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
34. exit(1);
35. }
36.
37. printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());
38.
39. pthread_exit((void *)0);
43.}
结果,产生参数错误
7.进程和线程控制原语对比
线程控制原语 -----------------进程控制原语
pthread_create() ----------------- fork();
pthread_self() -------------------- getpid();
pthread_exit() -------------------- exit(); / return
pthread_join() -------------------- wait()/waitpid()
pthread_cancel() ----------------- kill()
pthread_detach()----------------- 无
四.线程属性设置分离线程
pthread_create()的第二个参数,指定线程的属性,传入NULL表示默认属性,也可以传入线程属性结构体pthread_attr_t 来控制线程属性。
此次使用线程属性设置线程的线程分离
pthread_attr_t attr 创建一个线程属性结构体
pthread_attr_init(&attr); 初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 设置线程属性为分离态
pthread_create(&tid, &attr, tfn, NULL); 传入修改后的线程属性 创建为分离态的新线程
pthread_attr_destroy(&attr); 销毁线程属性