建设网站的app,2018什么做网站,wordpress chrome插件,建设网站的建议目录
1、线程与进程的关系
2、线程的优缺点
3、创建线程
4、查看启动的线程
5、验证线程是共享地址空间的
6、pthread_create的重要形参
6.1 线程id
6.2 线程实参
7、线程等待
8、线程退出
9、线程取消
10、线程tcb
10.1 线程栈
11、创建多线程
12、__th…目录
1、线程与进程的关系
2、线程的优缺点
3、创建线程
4、查看启动的线程
5、验证线程是共享地址空间的
6、pthread_create的重要形参
6.1 线程id
6.2 线程实参
7、线程等待
8、线程退出
9、线程取消
10、线程tcb
10.1 线程栈
11、创建多线程
12、__thread
13、线程分离
结语 前言 线程是操作系统进行调度的基本单位他属于进程的子集。在Linux下通过实现轻量化进程来实现线程因此线程具有进程的相关特性比如线程必须有自己的代码资源有属于自己独立的数据空间并且同一个进程下的线程所看到的地址空间是属于该进程的因为创建线程实际上就是在该进程下创建task_struct结构体该结构体的作用是方便操作系统对该执行流的调度这些task_struct结构体跟进程共用空间资源只不过线程可以在单一进程执行流的基础上实现多执行流并发式的运行代码以至于提高cpu的效率。
1、线程与进程的关系 说到线程就离不开进程的概念因为线程是在进程的基础上实现的多线程是底层就是创建了多个task_struct结构体作为进程的执行分支但是他们依然是共用进程的数据资源线程示意图如下 当系统里创建一个进程则系统需要给该进程分配新的地址空间、页表、物理内存等等空间资源所以说进程是系统分配资源的实体。但是当系统里有了新的线程则不会给线程分配新的空间资源而是给让线程使用进程的空间资源。 线程的独立部分 线程虽然和进程共用地址空间但是线程也有自己独立的部分比如线程ID 保存上下文的寄存器线程栈errnoblock信号集调度优先级。 2、线程的优缺点 线程的优点 1、当我们需要并发执行代码时创建一个线程的工作比创建一个进程的工作要小得多。 2、当cpu切换PCB时切换线程的效率比切换进程的效率略高。 3、线程所占用的资源小于进程。 4、线程之间的通信代价比进程的要小。 5、提高程序的并发性。 线程的缺点 1、若单个线程收到信号退出则会导致整个进程都退出。 2、多线程访问共同资源时是不受保护的会导致意料之外的错误。 3、编写多线程的难度很高。 4、若单个线程因为异常崩溃则会导致整个进程崩溃。 3、创建线程 创建线程需要用到的接口如下
#include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
//thread是一个执行类型为pthread_t的变量的指针
//attr是一个指针他指向的结构体包含新线程的各种属性设为nullptr则表示采用默认属性
//start_routine是新线程要执行的函数他接收一个void*返回值一个void*
//arg表示新线程要执行的函数的实参 创建线程前需要先定义一个类型为pthread_t的变量作为实参传递给函数pthread_create该变量的作用是让用户可以通过他找到对应的线程创建线程的代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;void *threadRun(void* args)
{while(1){cout 子线程: getpid() endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;//先定义一个pthread_t类型的变量//该函数调用完成后会赋予tid新的值表示该线程的idpthread_create(tid, nullptr, threadRun, nullptr);while(1){cout 主线程: getpid() endl;sleep(1);}
} 测试结果 注意使用线程的接口时要在编译的时候要手动链接pthread库如下图 4、查看启动的线程 在Linux下使用指令ps -aL就可以查看用户启动的线程了。如下图 LWP表示轻量级进程的pid即线程的pidLWP是给系统调度线程专门设置的标识符。
5、验证线程是共享地址空间的 定义一个全局变量若所有线程只能看到唯一一份全局变量那么就可以证明进程下的所有线程用的是同一个地址空间代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;int g_val 10;
// 新线程
void *threadRoutine(void *args)
{while (true){printf(子线程 pid: %d, g_val: %d, \g_val: 0x%p\n, getpid(), g_val, g_val);sleep(1);}
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRoutine, nullptr); while (true){printf(主线程 pid: %d, g_val: %d,\g_val: 0x%p,\n, getpid(), g_val, g_val);sleep(1);g_val;}return 0;
} 运行结果 从结果可以看到哪怕主线程对全局变量进行更改其他线程拿到的值是更改后的值若是父子进程关系则另一方会发生写时拷贝线程之间没有这么做说明线程是共享地址空间的。
6、pthread_create的重要形参
6.1 线程id 线程有两个标识符一个是LWP是给系统看的另一个是线程id是给用户看的。线程id就是创建线程时定义的pthread_t类型的变量该变量作为pthread_create的输出型参数在调用完pthread_create后该变量保存的就是线程id了。 查看线程id的代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;int g_val 10;
// 新线程
void *threadRoutine(void *args)
{
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRoutine, nullptr); while (true){printf(主线程 pid: %d, g_val: %d,\g_val: 0x%p,子线程id:%p \n, getpid(), g_val, g_val,tid);sleep(1);g_val;}return 0;
} 运行结果 从测试结果发现线程id实际上就是一串地址这个地址就是线程在地址空间内的映射间接说明了线程的管理是在用户空间内的进行并不是由操作系统像管理进程PCB一般在内核空间进行具体看下文线程tcb。
6.2 线程实参 线程的任务就是执行pthread_create的函数指针并且该函数具有一个void*的形参那么如何使用该void*的形参呢 测试代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;// 新线程
void *threadRoutine(void *args)
{char *name static_castchar *(args);//需要强转while (true){cout name endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRoutine, (void *)线程1);//需要强转while (true){}return 0;
} 运行结果 7、线程等待 子线程退出时主线程也要对其进行等待等待的原因和父子进程一样防止内存泄漏和回收退出信息若不等待线程则线程的tast_struct会一直存在会造成不必要的资源浪费进行线程等待的接口介绍如下
#include pthread.hint pthread_join(pthread_t thread, void **retval);
//thread表示要等待的线程id
//retval是一个二级指针是一个输出型参数目的是拿到线程返回的void* 线程等待的测试代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;// 新线程
void *threadRoutine(void *args)
{int count 5;while (count){count--;sleep(1);}return (void*)1;
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRoutine, nullptr);void* retval;pthread_join(tid,retval);//等待子线程cout线程等待成功(long long)retvalendl;return 0;
} 运行结果 由于线程执行的函数虽然可以返回一个void*的变量但是我们却没有办法接收该返回值因为这不是一个简单的函数调用。所以只能通过调用pthread_join然后传递一个二级指针给他pthread_join就可以通过输出型参数把void*变量给带出来。
8、线程退出 进程退出常常用exit函数只要一个进程调用了exit函数则该进程就直接结束了。但是若想仅仅退出一个线程则不能用exit因为当一个线程用exit退出就会把整个进程退出。线程退出有专门的退出函数该函数介绍如下
#include pthread.hvoid pthread_exit(void *retval);
//该函数会退出当前线程并且返回一个void*变量线程退出测试代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;// 新线程
void *threadRoutine(void *args)
{int count 5;while (count){count--;sleep(1);}//return (void*)1;pthread_exit((void*)100);
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRoutine, nullptr);void* retval;pthread_join(tid,retval);cout线程等待成功(long long)retvalendl;return 0;
} 运行结果 9、线程取消 可以调用函数pthread_cancel可以在线程退出前取消该线程该函数介绍如下
#include pthread.hint pthread_cancel(pthread_t thread);
//thread表示要取消的线程 线程取消测试代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;// 新线程
void *threadRoutine(void *args)
{int count 5;while (count){count--;sleep(1);}pthread_exit((void*)100);
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRoutine, nullptr);pthread_cancel(tid);void* retval;pthread_join(tid,retval);cout线程等待成功(long long)retvalendl;return 0;
} 运行结果 如果一个线程被取消则该线程的退出码为-1。
10、线程tcb 上文讲到线程id是给用户看的通过上述代码打印线程id可以观察到线程id实际上就是一串虚拟地址线程id的值如下 分析id的地址可以发现该地址数属于进程地址空间的共享区内说明线程本身就是存储在用户空间内的但是我们自己并没有对线程做任何管理那么线程是如何被管理的呢可以从函数pthread_create得知当我们调用pthread_create后该函数就会返回一个线程id给到我们说明该函数内部会自己维护线程而该函数的实现是存储在线程库pthread.so里的而线程库会在程序运行起来时加载到内存并映射在共享区内所有可以得出一个结论线程由线程库维护并映射在地址空间的共享区内。 具体示意图如下 从上图可以发现tcb就是管理线程的结构体线程库以维护tcb从而维护线程而tid就是线程id他就是tcb的首地址这也就很好的解释了为什么可以通过线程id找到对应的线程了这个地址是线程库为用户申请也就是用户调用函数pthread_create后所得到的地址。并且不同的进程创建的线程都会被线程库在内存中统一管理只是这些线程会分别映射到他们的进程共享区中。
10.1 线程栈 从上图可以发现除了地址空间的栈空间外线程tcb中也维护一个名为线程栈的空间而线程栈是采用数组的方式模拟出来的这些模拟栈被保存在共享区由线程库来维护。栈与栈之间相互独立不可直接访问但是同一进程下的其他线程采用特别的方式也可以访问到对方的栈因为毕竟都在同一个地址空间内。
11、创建多线程 创建多线程的思路利用循环定义多个线程id但是由于新的循环会覆盖旧的线程id因此需要把每个线程id存入容器中方便后续等待线程时能够找到他们的线程id创建多线程代码如下
#include iostream
#include vector
#include unistd.h
#include pthread.husing namespace std;
#define NUM 4
struct threadData//给每个线程做标记
{string threadname;
};// 所有的线程执行的都是这个函数
void *threadRoutine(void *args)
{threadData *td static_castthreadData *(args);int i 5;while (i){cout 我是 td-threadname , pid: getpid() endl;sleep(1);i--;}delete td;return nullptr;
}void InitThreadData(threadData *td, int number)
{td-threadname 线程- to_string(number);
}int main()
{// 创建多线程vectorpthread_t tids;for (int i 0; i NUM; i){pthread_t tid;threadData *td new threadData;InitThreadData(td, i);//标记线程pthread_create(tid, nullptr, threadRoutine, td);tids.push_back(tid);}//线程等待 for (int i 0; i tids.size(); i){pthread_join(tids[i], nullptr);}return 0;
} 运行结果 12、__thread __thread只能修饰内置类型不能修饰自定义类型他修饰的变量对于所有线程是可见的有点类似全局变量但是他跟全局变量的区别在于__thread修饰的变量对于每个线程而言是独立的换句话说线程对该变量的修改不会影响其他线程所看到的值。 测试代码如下
#include iostream
#include pthread.h
#include unistd.husing namespace std;__thread int a 10;void *threadRun(void* args)
{while(1){cout 子线程: getpid() a的值:aendl;//线程对a进行sleep(1);}return nullptr;
}int main()
{pthread_t tid;//先定义一个pthread_t类型的变量//该函数调用完成后会赋予tid新的值表示该线程的idpthread_create(tid, nullptr, threadRun, nullptr);while(1){cout 主线程: getpid() a的值:a endl;//此处a的值还是10吗sleep(1);}
} 运行结果 从结果可以看到主线程的a是独立于子线程的a原因就是a作为全局变量被__thread修饰了因此所有线程都有一份独立的a。
13、线程分离 主线程创建的子线程退出后主线程若想拿到子进程退出的void*返回值则主线程要对其进行join等待操作但是若主线程不关心其返回值则就没必要进行等待因为等待也是一种负担。因此在这种情况下可以让该子线程自行分离即分离的子线程在退出后会自动释放空间资源。 分离的函数介绍如下
#include pthread.hint pthread_detach(pthread_t thread);
//thread表示要分离的线程线程分离测试代码如下
#include iostream
#include pthread.h
#include unistd.h
#include cstringusing namespace std;void *threadRun(void* args)
{pthread_detach(pthread_self());//pthread_self返回该线程的idint count 3;while(count--){cout 子线程: getpid() endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(tid, nullptr, threadRun, nullptr);sleep(1);//要保证pthread_detach在pthread_join之前触发int n pthread_join(tid, nullptr);printf(n %d, who 0x%x, why: %s\n, n, tid, strerror(n));
} 测试结果 n 22表示等待失败了说明这些线程已经被分离了。
结语 以上就是关于使用线程的讲解线程是核心思想是创建多个执行流让程序实现并行运行目的就是提高程序执行的效率本文主要讲述如何创建线程和使用线程包括线程的基本用法和概念线程本身涉及的知识非常广细节也特别多在复杂的多线程下往往要考虑更多的东西。 最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充谢谢大家