网站建设前的分析公司概况了解,注册公司网站流程,怎么做考试资料分享网站,深圳网站建设怎么选择本篇我将学习如何使用多线程。要使用多线程#xff0c;因为Linux没有给一般用户直接提供操作线程的接口#xff0c;我们使用的接口#xff0c;都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此#xff0c;需要引入-lpthread#xff0c;即连接原生…本篇我将学习如何使用多线程。要使用多线程因为Linux没有给一般用户直接提供操作线程的接口我们使用的接口都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此需要引入-lpthread即连接原生线程库。
原生线程库#include pthread.h
自动化构建工具
mythread:mythread.cgcc -o $ $^ -lpthread
.PHONY:clean
clean:rm -f mythread创建线程
创建线程。
功能创建一个新的线程
原型:int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void*(*start_routine)(void*), void* arg);
参数:
thread : 返回线程ID.
attr : 设置线程的属性attr为NULL表示使用默认属性.
start_routine : 是个函数地址线程启动后要执行的函数.
arg : 传给线程启动函数的参数.
返回值成功返回0失败返回错误码.
获取调用它的线程id。即哪个线程调用了它就能够获得自己的id。
函数原型pthread_t pthread_self(void);
功能获取一个线程id即谁调用它就获取谁的线程id
头文件:#include pthread.h
参数无
返回值成功返回这个id。这个函数总是成功的
创建一个线程代码如下
#include stdio.h
#include pthread.h
#include unistd.hvoid *thread_run(void *args)
{while(1){//使用pthread_self()获取idprintf(我是新线程[%s]我的线程ID是 %lu\n,(const char*)args,pthread_self());sleep(1);}
}int main()
{pthread_t tid;//第一个参数为线程id第二个为线程属性设置为NULL默认值//第三个参数是线程执行的方法第四个是传给第三个参数的参数pthread_create(tid,NULL,thread_run,(void*)new thread);while(1){printf(我是主线程我创建的线程ID是 %lu,我的线程ID是: %lu\n,tid,pthread_self());sleep(1);}return 0;
}
结果如下能看到两个线程的ID不一样那就证明了单个进程中存在着两个线程。 创建多个线程代码如下
使用数组来存放线程id。注意此时thread_run()函数被重入了
#include stdio.h
#include pthread.h
#include unistd.hvoid *thread_run(void *args)
{while(1){//使用pthread_self()获取id// printf(我是新线程[%s]我创建的线程ID是 %lu\n,(const char*)args,pthread_self());sleep(3);}
}int main()
{//创建五个线程保存在数组中pthread_t tid[5];//第一个参数为线程id第二个为线程属性设置为NULL默认值//第三个参数是线程执行的方法第四个是传给第三个参数的参数int i 0;for(i 0;i5;i){pthread_create(tid1,NULL,thread_run,(void*)new thread);}while(1){printf(我是主线程我的thread ID:%lu\n,pthread_self());printf(######################begin########################\n);for(i 0;i5;i){ printf(我是主线程我创建的线程[%d]ID是 %lu,我的线程ID是: %lu\n,i,tid[i],pthread_self());}printf(######################end######################\n);sleep(1);}return 0;
}
结果如下 通过ps -aL查看当前线程。可以看到PID和LWP相同的就是主线程其它的都是新线程。LWP是线程id。 线程等待
一般而言线程也是需要等待的如果不等待就可能会导致类似于僵尸进程的问题。
功能等待线程结束
原型:int pthread_join(pthread_t thread, void** value_ptr);
参数:
thread : 线程ID
value_ptr : 它指向一个指针后者指向线程的返回值
返回值成功返回0失败返回错误码
写一个简单的测试主线程在等待10秒后打印111.
#include stdio.h
#include pthread.h
#include unistd.hvoid *thread_run(void *args)
{int num *(int*)args;while(1){//使用pthread_self()获取idprintf(我是新线程[%d]我创建的线程ID是 %lu\n,num,pthread_self());sleep(10);break;}//随便返回一个值用于测试return (void*)111;
}#define NUM 1
int main()
{pthread_t tid[NUM];int i 0;for(i 0;iNUM;i){pthread_create(tid1,NULL,thread_run,(void*)i);sleep(1);}//指针变量可以当某个数据的容器void *status NULL;//获得退出信息pthread_join(tid[0],status);printf(ret: %d\n,(int)status);return 0;
}
线程只能一个个等。
线程终止
线程终止的方案有
1.函数中的return。对于这个方案有两种情况第一种情况是在main函数中的return此时代表进程和主线程都退出了。第二种情况是其它函数中的return代表该线程的退出。
2.使用函数pthread_exit().
功能线程终止
原型:void pthread_exit(void* value_ptr);
参数:
value_ptr : z指的是退出后的返回值也就是return X。value_ptr不要指向一个局部变量。
返回值无返回值跟进程一样线程结束的时候无法返回到它的调用者自身
#include stdio.h
#include pthread.h
#include unistd.hvoid *thread_run(void *args)
{int num *(int*)args;while(1){//使用pthread_self()获取idprintf(我是新线程[%d]我创建的线程ID是 %lu\n,num,pthread_self());sleep(2);break;}pthread_exit((void*)123);
}#define NUM 1
int main()
{ pthread_t tid[NUM];int i 0;for(i 0;iNUM;i){pthread_create(tid1,NULL,thread_run,(void*)i);sleep(1);}//指针变量可以当某个数据的容器void *status NULL;//获得退出信息pthread_join(tid[0],status);printf(ret: %d\n,(int)status);return 0;
}
如果使用exit()那么会将进程和全部线程都终止掉。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了函数退出代表函数栈帧被销毁从而这个内存单元也被销毁了。
3.使用pthread_cancel函数取消目标线程。
功能取消一个执行中的线程
原型int pthread_cancel(pthread_t thread);
参数
thread : 线程ID
返回值成功返回0失败返回错误码退出码为-1#include stdio.h
#include pthread.h
#include unistd.hvoid *thread_run(void *args)
{int num *(int*)args;while(1){//使用pthread_self()获取idprintf(我是新线程[%d]我创建的线程ID是 %lu\n,num,pthread_self());sleep(2);//break;}//pthread_exit((void*)123);//随便返回一个值用于测试//return (void*)111;
}#define NUM 1
int main()
{pthread_t tid[NUM];int i 0;for(i 0;iNUM;i){pthread_create(tid1,NULL,thread_run,(void*)i);sleep(1);}printf(wait sub thread...\n);//等5秒钟sleep(5);printf(cancel sub thread...\n);//取消线程pthread_cancel(tid[0]);//指针变量可以当某个数据的容器void *status NULL;//获得退出信息pthread_join(tid[0],status);printf(ret: %d\n,(int)status);return 0;
}
当一个新线程被取消后退出码为-1即PTHREAD_ CANCELED。
当把主线程取消但新线程没有被取消此时新线程依旧在运行着并且主线程会进入僵尸状态说明线程没有僵尸状态这个东东是有类似僵尸进程的问题。因此我们一般不能用新线程去取消主线程。 线程分离
默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。
如果不关心线程的返回值join是一种负担这个时候我们可以告诉系统当线程退出时自动释放线程资源。
线程分离后不需要被join终止只需运行结束后会自动释放Z。分离后的线程相对于是同一屋檐下的陌生人即这个线程在跟同一个进程内的线程毫无关系了此时一定不能对其join因为会失败。
可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离。
线程分离一般的应用场景是主线程不退出新线程处理完任务后退出。
功能分离线程
原型int pthread_detach(pthread_t thread);
参数
thread : 线程ID
返回值成功返回0
#include stdio.h
#include pthread.h
#include unistd.h
#include stdlib.hvoid *thread_run(void *args)
{//分离pthread_detach(pthread_self());int num *(int*)args;while(1){//使用pthread_self()获取idprintf(我是新线程[%d]我创建的线程ID是 %lu\n,num,pthread_self());sleep(2);break;}//随便返回一个值用于测试return (void*)111;
}#define NUM 1
int main()
{pthread_t tid[NUM];int i 0;for(i 0;iNUM;i){pthread_create(tid1,NULL,thread_run,(void*)i);sleep(1);}printf(wait sub thread...\n);//等5秒钟sleep(5);printf(cancel sub thread...\n);//指针变量可以当某个数据的容器void *status NULL;int ret 0;//获得退出信息ret pthread_join(tid[0],status);printf(ret: %d,status: %d\n,ret,(int)status);return 0;
}LPW的解释 在使用ps -aL查看线程情况时LWP为内核LWP我们最好不要叫它线程ID因为在Linux中没有线程这玩意我们所说的线程都是进程PCB模拟出来的属于轻量级进程。
对于LWP它的值跟我们在测试代码时得出的结果线程的ID不一样一个是原生线程库的一个是内核的。
下面将好好分析一下原生线程库中的线程pid的本质。
先来说结果我们通过pthread_self()获取的线程id其实是虚拟内存地址 我们都知道每一个线程都要有运行的临时数据因此每个线程都要有自己的私有栈结构也需要拥有描述线程的用户控制块但是在虚拟地址空间中的栈结构不可能会分成很多份给每一个线程的它是属于主线程和进程的
每一个新线程所拥有的栈结构等等其实都是由原生线程库提供的每一个线程跟每一个库提供的线程栈和线程局部存储等组成的用户控制块都是一一对应的是以11的比例对对应着
那么如何区找到需要找到的线程就需要用到一个地址去找并且每一个描述线程的用户控制块都会保存着每一个线程对应的PWD这个地址就是每一个用户控制块的地址
总结 ①pthread_ create函数会产生一个线程ID存放在第一个参数指向的地址中。该线程ID和内核LWP不是一回事前者是原生线程库中的一个是内核LWP。 ②前面讲的LWP线程ID属于进程调度的范畴。因为线程是轻量级进程是操作系统调度器的最小单位所以需要一个数值来唯一表示该线程。 ③pthread_ create函数第一个参数指向一个虚拟内存单元该内存单元的地址即为新创建线程的线程ID属于NPTL线程库的范畴。线程库的后续操作就是根据该线程ID来操作线程的。 ④线程库NPTL提供了pthread_ self函数可以获得线程自身的ID。