目录
线程概念/特点/优缺点/与进程比较 写在另一篇博客
戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/104571277
POSIX线程库
POSIX线程(POSIX Threads, 常缩写为Pthreads)是POSIX的线程标准, 定义了创建和操纵线程的一套API(应用程序编程接口).
由于Linux内核不区分线程和进程, 所以操作系统并没有向用户提供创建线程的接口, 为此大佬们封装了一套线程控制接口
- POSIX线程库中绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要加头文<pthread.h>
- 在最后编译链接时, 要添加 -pthread或-lpthread选项, 用来链接POSIX线程库(注 : gcc4.5.2往后就已经没有-lpthread的介绍了, 所以往后选用-pthread)
创建线程
与创建进程的函数fork()类似, 其实fork()和pthread_create()内部都调用了clone().
原型 : int pthread_create(pthread_t* tid, const pthread_attr_t* attr, void* (*start_routine)(void* arg), void* arg) ;
功能 : 创建一个新线程
参数 : tid : 输出型参数, 获取线程id(这个线程id下面细说)
attr : 设置线程的属性,传入NULL表示使用默认属性
start_routine : 是一个函数指针, 传入线程入口函数 , 其中arg是线程入口函数的形参( 创建一个线程就是为了运行传 入的函数指针所指向的函数, 函数运行完毕, 则线程退出)
arg : 是传入的线程入口函数的参数返回值 : 返回值为0, 表示创建成功, 返回值 > 0, 则表示失败, 返回的是errno
来看个例子 .
-
#include<iostream>
-
#include<cstdio>
-
#include<cstring>
-
#include<unistd.h>
-
#include<pthread.h>
-
void* thread_start(void* arg){
-
while(
1){
-
printf(
"主线程向我传递了一个参数:%s\n", (
char*)arg);
-
sleep(
1);
-
}
-
return
NULL;
-
}
-
int main(){
-
pthread_t tid;
-
char buf[] =
"哈哈哈哈";
-
int ret = pthread_create(&tid,
NULL, thread_start, (
void*)buf);
-
if(ret){
-
fprintf(
stderr,
"pthread_create: %s\n", strerror(ret));
//strerror函数
-
//perror("pthread_create:");//如果置全局的errno, 则可以直接用perror,
-
//但pthread_create并不会改变全局的errno, 而是将errno返回
-
return
-1;
-
}
-
while(
1){
-
printf(
"我是主线程\n");
-
sleep(
1);
-
}
-
return
0;
-
}
线程ID及进程地址空间布局
线程id和进程id
在没有学习线程之前, 我们将PCB中的pid作为一个进程的id来标识一个进程, 但当引入线程的概念后, 就不能再这样理解. 当
一个进程有多个线程时, 每个线程在内核中都有一个PCB, 每个PCB都有唯一的pid, 那么此时的进程id又是什么呢, 其实这
时候我们所说的进程id是就是PCB中tgid (thread group id), 同组线程PCB中的tgid都是相同的, 对应的就是我们能看见的进程
id, 而线程id就是其各自PCB唯一的pid(之前我们认为的进程id变为了现在的线程id), 就拿上面的图为例.
再来看pthread_create() 函数中所获取的线程id
- pthread_ create函数会产生一个线程id, 存放在第一个参数指向的地址中. 这里的线程id和上面所说的线程id不是一回事.
- 上面所说的线程id属于内核调度的范畴, 因为线程是轻量级进程, 是操作系统调度的最小单位, 所以需要一个唯一标识 .
- 而这里pthread_create()函数第一个参数返回的值是指向一个虚拟内存单元的指针, 也就是说这个内存单元的首地址即为新创建线程的线程id
- 那么这个线程id(指针)指向的内存单元到底是什么呢?由于线程库函数并不是内核提供的, 所以线程库函数无法直接对内核中
线程的PCB操作, 所以在创建线程时, 在虚拟内存空间上也分配了一块空间来存储线程描述信息, 线程库函数通过在找到线程描述信息进而访问在内核中的线程的PCB, 现在就比较清楚了, 这里的线程id纯粹就是为了线程库函数要完成对线程的控制而存在的. 当脱离线程库函数后就失去意义.
(mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一 一对映关系)
- 线程库中其他函数的后续操作, 就是根据该线程ID来操作线程的.
- 这样pthread_t 的类型也就知道了, pthread_t是一个指针类型.
pthread_ self函数,可以获得线程自身的ID
原型 : pthread_t pthread_self( )
功能 : 获取线程自身id
返回值 : 线程自身id
-
#include<iostream>
-
#include<cstdio>
-
#include<cstring>
-
#include<unistd.h>
-
#include<pthread.h>
-
void* thread_start(void* arg){
-
sleep(
1);
-
printf(
"ID:%p,主线程向我传递了一个参数:%s\n", pthread_self(), (
char*)arg);
-
return
NULL;
-
}
-
int main(){
-
pthread_t tid;
-
char buf[] =
"哈哈哈哈";
-
int ret = pthread_create(&tid,
NULL, thread_start, (
void*)buf);
-
if(ret){
-
fprintf(
stderr,
"pthread_create: %s\n", strerror(ret));
//strerror函数
-
//perror("pthread_create:");//如果置全局的errno, 则可以直接用perror,
-
//但pthread_create并不会改变全局的errno, 而是将errno返回
-
return
-1;
-
}
-
printf(
"我是主线程, ID:%p, 创建的线程ID:%p\n", pthread_self(), tid);
-
sleep(
1);
-
return
0;
-
}
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
- 线程函数中 return. 这种方法对主线程不适用, 从main函数中return相当于调用exit(), 是退出进程
- 线程调用库函数pthread_ exit()终止自己.
- 一个线程可以调用pthread_ cancel()可以终止同一进程中(线程组中)的另一个线程.
具体来看
原型 : void pthread_exit(void* retval)
功能 : 谁调用, 谁退出
参数 : retval : 输出型参数, 返回线程的退出状态, 不关心可以置NULL.
返回值 : 无
注意 : 不能将retval指向线程函数内的局部变量(临时变量), 一般是指向全局变量或是堆区的变量, return也是同样的道理.
原型 : int pthread_cancel(pthread_t tid)
功能 : 退出同组线程中, 线程id是tid的线程
参数 : tid : 线程id
返回值 : 为0, 则成功, 返回值>0, 返回的是错误码errno
注意 : 在线程入口函数中可以使用exit() 或 _exit()吗 ? 在线程中使用它们两都说使整个进程退出(线程组都退出). 所以一般不用.
线程等待
不难发现, 当return 或者pthread_exie()时, 返回的结果去哪儿了 ? 其实, 当线程退出后, 其空间并没有释放, 任然在进程的地
址空间内, 创建新的线程不会再复用刚才退出线程的地址空间(因为没有释放啊), 所以为了获取线程返回状态, 我们需要线程
等待.
原型 : int pthread_join(pthread_t tid, void** retval)
功能 : 等待指定线程退出, 获取其退出状态
参数 : tid : 需要等待退出的线程id
retval : 输出型参数, 二级指针, 指向线程退出时返回的指针, 线程退出时返回的指针指向线程退出状态(返回值).返回值 : 成功返回0, 失败返回值>0, 返回的是错误码errno
注意 : 调用这个函数的线程将挂起等待(谁调用的谁挂起等待), 直到id为tid的线程终止. 线程id是tid的线程以不同的方法终止,
通过pthread_join得到的终止状态是不同的,分别如下:
- return 退出, retval指向的是 return 返回的值
- pthread_exit() 退出时, retval指向的是pthread_exit()的参数
- 被pthread_cancel异常终止, retval的值是宏 PTHREAD_ CANCELED
- 当不关心线程的推出状态时, retval的可以置NULL
举个栗子 :
-
#include<iostream>
-
#include<cstdio>
-
#include<cstring>
-
#include<unistd.h>
-
#include<pthread.h>
-
using
namespace
std;
-
int n =
2;
-
void* thread_start1(void* arg){
-
int* p =
new
int;
-
*p =
1;
-
return (
void*)p;
-
}
-
void* thread_start2(void* arg){
-
int *p = &n;
-
pthread_exit((
void*)p);
-
}
-
void* thread_start3(void* arg){
-
while(
1){
-
sleep(
1);
-
printf(
"thread3 is runing ...\n");
-
}
-
return
NULL;
-
}
-
-
int main(){
-
pthread_t tid[
3];
-
void* (*thread_start[
3])(
void*) = {thread_start1, thread_start2, thread_start3};
-
int ret;
-
for(
int i =
0; i <
3; ++i){
-
ret = pthread_create(&tid[i],
NULL, thread_start[i],
NULL);
-
if(ret){
-
fprintf(
stderr,
"pthread_create%d: %s\n", i +
1, strerror(ret));
-
return
-1;
-
}
-
}
-
int* retval;
-
pthread_join(tid[
0], (
void**)&retval);
-
printf(
"thread%d返回值为%d\n",
1, *retval);
-
delete retval;
-
pthread_join(tid[
1], (
void**)&retval);
-
printf(
"thread%d返回值为%d\n",
2, *retval);
-
sleep(
3);
-
pthread_cancel(tid[
2]);
-
pthread_join(tid[
2], (
void**)&retval);
-
if(retval == PTHREAD_CANCELED){
-
printf(
"thread3 return code:PTHREAD_CANCELED\n");
-
}
-
else{
-
printf(
"thread%d返回值为%d\n",
2, *retval);
-
}
-
return
0;
-
}
线程分离
默认情况下,pthread_create() 新创建的线程是joinable的, 线程退出后, 需要对其进行pthread_join操作, 否则无法释放资源, 会造成内存泄漏 .
但当我们不关心线程的返回值, joinable属性就成为了一种负担, 这个时候, 我们可以告诉系统, 当线程退出时, 自动释放线程资源. 我们就不需要再管了.
原型 : int pthread_detach(pthread_t tid)
功能 : 分离指定的线程(将指定的线程属性设置为detach)
参数 : tid : 需要分离的线程id
返回值 : 成功返回0, 失败返回值>0, 返回的是错误码errno
注意 :
- pthread_detach() 可以是线程组内其他线程对目标线程进行分离 : pthread_detach(tid) //tid为要分离的同组线程id
- 也可以是线程自己分离 : pthread_detach(pthread_self())
- 线程分离只是给线程更改一下属性, 所以可以在线程入口函数中让线程自己分离自己, 也可以在主线程中创建线程之后直接分离
- 一个线程的属性不能使即joinable又detach. 当线程被分离后就不能再pthread_join()了. 等待一个已经被分离的线程, pyhread_join会返回错误
举个栗子 :
-
#include<iostream>
-
#include <cstdio>
-
#include <cstring>
-
#include <unistd.h>
-
#include <pthread.h>
-
using
namespace
std;
-
void* thread_start1(void* arg) {
-
pthread_t tid = pthread_self();
-
int ret = pthread_detach(tid);
-
if(ret){
-
fprintf(
stderr,
"pthread_detach thread1:%s\n", strerror(ret));
-
}
-
else{
-
printf(
"thread1, id:%p已分离\n", tid);
-
}
-
usleep(
1000);
-
return
NULL;
-
}
-
void* thread_start2(void* arg){
-
usleep(
1000);
-
return
NULL;
-
}
-
void* thread_start3(void* arg){
-
pthread_t tid = pthread_self();
-
int ret = pthread_detach(tid);
-
if(ret){
-
fprintf(
stderr,
"pthread_detach thread3:%s\n", strerror(ret));
-
}
-
else{
-
printf(
"thread3, id:%p已分离\n", tid);
-
}
-
usleep(
1000);
-
return
NULL;
-
}
-
int main() {
-
pthread_t tid;
-
//thread1
-
int ret = pthread_create(&tid,
NULL, thread_start1,
NULL);
-
if (ret){
-
fprintf(
stderr,
"pthread_create thread1:%s\n", strerror(ret));
-
return
-1;
-
}
-
//thread2
-
usleep(
100);
-
ret = pthread_create(&tid,
NULL, thread_start2,
NULL);
-
if (ret){
-
fprintf(
stderr,
"pthread_create thread2:%s\n", strerror(ret));
-
return
-1;
-
}
-
ret = pthread_detach(tid);
-
if(ret){
-
fprintf(
stderr,
"pthread_detach thread2:%s\n", strerror(ret));
-
}
-
else{
-
printf(
"thread2, id:%p已分离\n", tid);
-
}
-
//thread3
-
ret = pthread_create(&tid,
NULL, thread_start3,
nullptr);
-
if (ret){
-
fprintf(
stderr,
"pthread_create thread3:%s\n", strerror(ret));
-
return
-1;
-
}
-
usleep(
1000);
//必须要睡一下,要让线程先分离,再等待
-
//线程被创建, 但创建的线程中的分离语句和下面的join语句
-
//哪个先执行还真不一定, 所以说要先睡一下
-
ret = pthread_join(tid,
NULL);
-
if (ret ==
0) {
-
printf(
"pthread3 wait success\n");
-
-
}
else {
-
fprintf(
stderr,
"pthread3 wait failed:%s\n", strerror(ret));
-
}
-
return
0;
-
}
转载:https://blog.csdn.net/qq_41071068/article/details/104613224