Linux系统多线程应用API详解(一)---线程的创建和销毁
sinye56 2024-11-06 12:01 5 浏览 0 评论
一、线程介绍:
1. 进程是资源管理的基本单元,而线程是系统调度的基本单元, 线程是操作系统能够进行调度运算的最小单位,它被包含在进程之中.
2. 一个完整的线程/进程包括三部分,代码+数据+内存栈. 子线程和子进程在被创建的时候,对于fork()创建子进程, 三部分都要复制一份,数据包括比如文件描述符, 虚拟内存, 子进程关闭文件描述符不会影响父进程中的描述符; 对于pthread_create()创建子线程的时候,只有内存栈被复制,其他的部分(代码,数据都是共享的), 如果一个线程改变了某变量的值, 其他所有的线程都调用的是改变之后的值;
3. 所以在Linux系统中,创建多线程是一种非常重要且常见的并发编程技术, 可以实现多个任务的同时执行,提高程序的性能和效率。
4. 编程涉及到的内容:
数据类型:pthread_t, pthread_attr_t, pthread_cond_t, pthread_mutexattr_t, void* (*)(void*),
头文件: #include <pthread.h>
编译参数: -lpthread
下面将详细介绍在Linux环境下与多线程编程开发相关的一些API接口。
二、线程的创建和销毁:
---pthread_create. 线程的创建:
int pthread_create(pthread_t *tid, const pthread_arrt_t* attr,
void*(*start_routine)(void *), void* arg);
1)说明:此函数用于线程的创建,实际上就是确定调用该线程函数的入口点,在线程创建以后,就开始运行相关的线程函数;
2)参数:
---tid:指向线程标识符的指针,由函数创建成功后填充此值;
---attr:用于手动设置新建线程的属性,例如线程的调用策略、线程所能使用的栈内存的大小等。
通常情况下将 attr 参数赋值为 NULL,函数会采用系统默认的属性值创建线程。
---void *(*start_routine) (void *):以函数指针的方式指明新建线程需要执行的函数,
使用此类型指针时,我们自定义的函数格式(参数和返回值)要与函数指针保持一致;
---void *arg:线程运行函数的参数;
---返回值:如果成功创建线程,函数返回数字0,反之返回非零值,有以下几种情况:
EAGAIN:系统资源不足,无法提供创建线程所需的资源。
EINVAL:传递给 pthread_create() 函数的 attr 参数无效。
EPERM:传递给 pthread_create() 函数的 attr 参数中,某些属性的设置为非法操作,没有相关的设置权限。
注:以上这些宏都声明在 <errno.h> 头文件中,需提前引入此头文件。
3)例子:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
void *thread_func(void *lpvoid)
{
int val = *((int *)lpvoid);
printids("new thread! get param:%d. \n", val);
return NULL;
}
int main(int argc, char **argv)
{
int err;
pthread_t threadId;
int val = 100;
err = pthread_create(&threadId, NULL, thread_func, (void*)&val);
if (err != 0)
printf("can't create thread: %s\n", strerror(err));
pthread_join (threadId, NULL);
return EXIT_SUCCESS;
}
---pthread_join. 一个线程等待另一个线程结束
int pthread_join(pthread_t thread, void **retval);
1)说明: 等待子线程的结束,非分离的线程在结束后只有执行join才会释放全部资源。 在很多情况下,主线程生成并启动一个子线程,用来处理一些耗时的工作,所以主线程往往会在子线程之前结束,
另外如果主线程需要获取子线程的处理结果, 也会等待子线程执行完成之后再结束,这种场景就要用到pthread_join()方法。
2)参数:
--pthread_t thread: 被连接线程的线程号
--void **retval : 指向一个指向被连接线程的返回码的指针的指针;具体值由函数来填充;
--返回值:线程连接的状态,0是成功,非0是失败。
3)例子:
a. 如果我们在调用pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线程采用默认的属性,也就是joinable。
-------------------例子1:非分离线程------------
pthread_t tid;
pthread_create(&tid, NULL, thread_run, NULL);
/*创建线程之后直接调用pthread_join方法等待thread_run子线程的结束。*/
pthread_join(tid,NULL);
b.如果需要将属性设置为detached, 在线程设置为joinable后,可以调用pthread_detach()使之成为detached。但是反相操作则不可以。
如果线程已经调用pthread_join( )后,则再调用pthread_detach( )则不会有任何效果。
-------------------例子2:分离线程------------
void* start_run(void* arg)
{
//dosomework
}
int main()
{
pthread_t thread_id;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(&thread_id,&attr,start_run,NULL);
pthread_attr_destroy(&attr);
sleep(5);
///!!不需要调用这个函数
//pthread_join (threadId, NULL);
exit(0);
}
4)补充说明:
a. 子线程中被释放的内存空间仅仅是系统空间,你必须手动清除程序分配的用户空间,比如 malloc() 分配的空间。
b. 一个线程只能被一个线程所连接join;
c. 被连接的线程必须是非分离的,否则连接会出错。
所以可以看出pthread_join()有两种作用:
(1).用于等待其他线程结束:当调用此函数时,当前线程会处于阻塞状态,直到被调用的线程结束后;
(2).对线程的资源进行回收:如果一个线程是非分离的且没有调用此等待函数,线程结束后并不会释放其内存空间, 这会导致该线程变成了“僵尸线程”。
---pthread_self.获取线程的pid
pthread_t pthread_self(void);
1)说明: 新建线程在后续运行中如果想要获取自身的线程ID,可以通过Pthread库提供的pthread_self()函数来返回。此值即为
pthread_create函数新建线程中指向的线程ID;
2)参数:
---返回值:pthread_create函数新建线程中指向的线程ID;
3)例子:
pthread_mutex_t mutex;
void* threadFunc(void* obj)
{
pthread_mutex_lock(&mutex);
pthread_t thread_id = pthread_self();
printf("2---子线程ID为:%d\n", thread_id);
pthread_mutex_unlock(&mutex);
return nullptr;
}
int main(int argc,char* argv[])
{
pthread_t threadId;
pthread_create(&threadId, nullptr,&threadFunc,nullptr);
pthread_mutex_lock(&mutex);
printf("1---子线程ID为:%d\n", threadId);
pthread_mutex_unlock(&mutex);
pthread_join(threadId, NULL);
return 0;
}
---pthread_detach.线程分离:
int pthread_detach(pthread_t thread);
1)说明: 线程设置为分离, 当此线程退出时, 不会向任何其他线程传递返回值, 分离的线程无法被 pthread_join();
2)参数:
---thread:需要分离的线程ID
---返回值:成功:0;失败:错误号。
3)例子:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* threadFunction(void* arg) {
printf("Thread started\n");
sleep(5);
printf("Thread finished\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, threadFunction, NULL);
// 将子线程标记为分离状态
pthread_detach(thread);
printf("Main thread finished\n");
return 0;
}
4)注意:
a. 线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取, 而直接自己自动释放。网络、多线程服务器常用。
b. 不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误
---pthread_exit.线程终止函数。
int pthread_exit(void* value_ptr);
1)说明:用于在线程内部显式地退出线程并返回终止状态。
2)参数:
---value_ptr:是一个指向线程的返回值的指针。它可以用于向等待该线程的其他线程传递一个返回值。 如果不需要传递返回值,可以将value_ptr设置为NULL。 当调用pthread_exit()时,当前线程会立即终止,并将value_ptr指向的值作为线程的终止状态传递给等待该线程的其他线程。 如果不等待线程退出,它的终止状态将被丢弃。
3)例子:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void* threadFunction(void* arg) {
/*传入的参数作为返回值*/
int* value_ptr = (int*)arg;
printf("Thread function executed\n");
// 结束线程,并返回传入的值
pthread_exit((void*)value_ptr);
}
int main() {
pthread_t thread;
int retVal = 42;
pthread_create(&thread, NULL, threadFunction, (void*)&retVal);
// 等待子线程结束,并获取其返回值
void* exitStatus;
pthread_join(thread, &exitStatus);
int* threadRetVal = (int*)exitStatus;
// 输出子线程的返回值
printf("Thread exit status: %d\n", *threadRetVal);
return 0;
}
---pthread_cancel.线程取消:
int pthread_cancel (pthread_t __th);
1)说明:函数是一个用于取消线程执行的函数,它允许一个线程(调用者线程)请求取消另一个线程的执行。 线程的取消并不是实时的,会有一定的延时。线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用。
2)参数:
---thread:线程ID。
---返回值:成功:0;失败:错误号。
3)使用条件:
a. 接收到取消请求的目标线程可以决定是否允许被取消以及如何取消,这分别由如下两个函数完成:
--int pthread_setcancelstate (int __state, int *__oldstate);
-- int pthread_setcanceltype (int __type, int *__oldtype);
b. 这两个函数的第一个参数分别用于设置线程的取消状态(是否允许取消)和取消类型(如何取消),
第二个参数则分别记录线程原来的取消状态和取消类型。
c. state 参数有两个可选值:
--PTHREAD_CANCEL_ENABLE,允许线程被取消。它是线程被创建时的默认取消状态。
--PTHREAD_CANCEL_DISABLE,禁止线程被取消。这种情况下,如果一个线程收到取消请求,则它会将请求挂起,直到该线程允许被取消。
d. t ype参数也有两个可选值:
--PTHREAD_CANCEL_ASYNCHRONOUS,线程随时都可以被取消。它将使得接收到取消请求的目标线程立即采取行动。
--PTHREAD_CANCEL_DEFERRED, 允许目标线程推迟行动,直到它调用了下面几个所谓的取消点函数中的一个: pthread_join、 pthread_testcancel、 pthread_cond_wait、pthread_cond_timedwait、sem_wait 和sigwait。
e. 其它的取消点:
根据POSIX标准,其他可能阻塞的系统调用,比如read、wait也可以成为取消点。不过为了安全起见,我们最好在可能会被取消的代码中调用pthread_testcancel 函数以设置取消点pthread_setcancelstate和pthread_setcanceltype 成功时返回0,失败则返回错误码。
4)例子:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* threadFunction(void* arg)
{
// 设置取消状态为允许取消
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 设置取消类型为异步取消
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
printf("Thread started\n");
while (1) {
printf("Thread is running\n");
sleep(1);
}
printf("Thread finished\n");
return NULL;
}
////////////////////MAIN FUNCTION ///////////////////////////////
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, threadFunction, NULL);
// 主线程休眠5秒后取消子线程
sleep(5);
pthread_cancel(thread);
// 等待子线程结束
pthread_join(thread, NULL);
printf("Main thread finished\n");
return 0;
}
---pthread_testcancel .设置线程取消点:
void pthread_testcancel (void);
1)说明:上面提到,被终止的线程必须要有取消点,如果没有取消点,这里可以自己手动设置一个取消点,这样的话无需 sleep等这样的阻塞函数也可以实现线程取消了。
2)参数:无
3)例子:
void *thread_run(void *arg)
{
printf("This is child thread\n");
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL); //禁止取消
// while(1)
{
sleep(5);
pthread_testcancel(); //设置取消点
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); //使能取消
while(1){
sleep(1);
}
pthread_exit("thread return");
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_run, NULL);
pthread_cancel(tid); // 取消子线程
}
---.检测线程是否退出:
int pthread_tryjoin_np (pthread_t threadId, void **__thread_return);
1)说明:
函数与线程线程执行非阻塞连接,并在*retval中返回线程的退出状态。如果线程尚未终止,则调用将返回错误,而不是像pthread_join(3)那样阻塞。
2)参数:与pthread_join参数一样;
3)例子:使用方法与 pthread_joing 一样,注意区别是非阻塞的。
/* 创建新线程*/
pthread_t t_id = -1;
pthread_create( &t_id, NULL, func, NULL); 54
/* 取消线程请求 */
sleep(1);
pthread_cancel(t_id);
/* 取消线程后,看看能否再结合。(结果是不能结合)*/
int *retval;
/* tryjoin不阻塞,错误直接返回*/
int ret_tryjoin = pthread_tryjoin_np(t_id, (void *)&retval);
if(ret_tryjoin)
{
fprintf(stderr, "线程被取消,结合失败!\n返回值:%s\n", strerror(ret_tryjoin) );
}
else
{
printf("线程结合,func 退出值:%d\n", *retval);
}
---.线程等待结束:
int pthread_timedjoin_np (pthread_t threadId, void **thread_return, const struct timespec *__abstime);
1)说明:
函数执行超时联接。如果线程尚未终止,则调用将阻塞直到abstime中指定的最大时间。
如果超时在线程终止之前到期,则调用将返回错误。
2)参数:
---threadId: 线程ID号;
---thread_return: 线程退出时的返回值;
---abstime:是struct timespec形式的结构体,用于指定自大纪元以来测量的绝对时间。
---返回值:成功时,这些函数返回0;否则,返回0。错误时,它们返回错误号。
3)例子:
struct timespec ts;
int s;
...
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
/* Handle error */
}
ts.tv_sec += 5;
s = pthread_timedjoin_np(thread, NULL, &ts);
if (s != 0) {
/* Handle error */
}
---.线程的清理:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
1)说明:当线程非正常终止,需要清理一些资源。
routine 函数被执行的条件:
a. 被pthread_cancel取消掉。
b. 执行pthread_exit
c. 非0参数执行pthread_cleanup_pop()
2)参数:
---void (*routine) (void *):执行退出前的清理函数;
---arg: 清理函数所带的参数;
---execute: 当为1时执行,为0时不执行;
3)例子
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void cleanup(void *arg){
printf("cleanup,arg=%s\n",(char*)arg);
}
void *func(void *arg){
printf("This is child thread\n");
pthread_cleanup_push(cleanup, "abcd");
pthread_exit("thread return");
while(1){
sleep(1);
}
pthread_cleanup_pop(0);
}
int main(){
pthread_t tid;
void *retv;
int i;
pthread_create(&tid, NULL, func, NULL);
sleep(1);
pthread_cancel(tid);
pthread_join(tid,&retv);
//printf("thread ret=%s\n",(char*)retv);
while(1){
sleep(1);
}
}
4)注意事项:
a. 必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。
b. pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push回调函数routine不会被执行.
c. pthread_cleanup_push 和pthread_cleanup_pop可以写多对,routine执行顺序正好相反
d. 线程内的return可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。
---.线程比较:
int pthread_equal (pthread_t __thread1, pthread_t __thread2);
1)说明:比较两个线程ID是否相等。
2)参数:
__thread1: 其中一个线程的ID号;
__thread2: 待比较的线程ID号;
---返回值:0--不相等;非0--相等;
3)例子:
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, func, NULL);
pthread_create(&thread2, NULL, func, NULL);
int ret = pthread_equal(thread1, thread2);
if(ret)
printf ("The to threads ID is equal! \n");
else
printf ("The to threads ID is not equal! \n");
---.创建/ 删除线程的私有数据
int pthread_key_create(pthread_key_t *key, void(*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
1)说明: 这个函数的作用主要是创建一个全局的,所有线程可见的一个 key ,然后所有的线程可以通过这个 key 创建一个线程私有的数据,并且我们可以设置一个析构函数 .
2)参数:
---key: 为指向一个键值的指针;
---destructor:指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
3)例子:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_key_t key;
void key_destructor1(void* arg)
{
printf("arg = %d thread id = %lu\n", *(int*)arg, pthread_self());
free(arg);
}
void thread_local()
{
int* q = pthread_getspecific(key);
printf("q == %d thread id = %lu\n", *q, pthread_self());
}
void* thread_func1(void* arg)
{
printf("Entering thread_func1\n");
int* s = malloc(sizeof(int));
*s = 100;
pthread_setspecific(key, s);
thread_local();
printf("Leaving thread_func1\n");
return NULL;
}
void* thread_func2(void* arg)
{
printf("Entering thread_func2\n");
int* s = malloc(sizeof(int));
*s = -100;
pthread_setspecific(key, s);
thread_local();
printf("Leaving thread_func2\n");
return NULL;
}
/*------------main function */
int main(int argc, char **argv)
{
pthread_key_create(&key, key_destructor1);
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func1, NULL);
pthread_create(&t2, NULL, thread_func2, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_key_delete(key);
return 0;
}
---.设置/获取对应key的具体数据
int pthread_setspecific(pthread_key_t key, const void * value);
void * pthread_getspecific(pthread_key_t key);
1)说明: 通过这两个函数可以设置/获取对应 key 的具体的数据。
2)参数:
---key: 为前面函数pthread_key_create创建的pthread_key_t变量;
---value: 这是一个void*无类型变量,用来存储任何类型的值。
3)例子:(具体使用方法见上面例子)
(下一篇介绍线程相关的属性设置API接口)
相关推荐
- Linux两种光驱自动挂载的方法
-
环境:CentOS6.4西昆云服务器方式一修改fstab文件/etc/fstab是系统保存文件系统信息?静态文件,每一行描述一个文件系统;系统每次启动会读取此文件信息以确定需要挂载哪些文件系统。参...
- linux系统运维,挂载和分区概念太难?在虚机下操作一次全掌握
-
虚拟机的好处就是可以模拟和学习生产环境的一切操作,假如我们还不熟悉磁盘操作,那先在虚机环境下多操作几次。这次来练习下硬盘扩容操作。虚拟机环境:centos8vm11linux设备命名规则在linux中...
- Linux 挂载 NFS 外部存储 (mount 和 /etc/fstab)
-
mount:手工挂载,下次重启需再重新挂载,操作命令:mount-tnfs-ooptionsserver:/remote/export/local/directory上面命令中,本地目录...
- 在Linux中如何设置自动挂载特定文件系统(示例)
-
Linux...
- Linux环境中的绑定挂载(bind mount)
-
简介:Linux中的mount命令是一个特殊的指令,主要用于挂载文件目录。而绑定挂载(bindmount)命令更为特别。mount的bind选项将第一个目录克隆到第二个。一个目录中的改变将会在...
- Linux挂载CIFS共享 临时挂载 1. 首先
-
如何解决服务器存储空间不足的问题?大家好,欢迎回来。在上一期视频中,我为大家介绍了如何利用Linux挂载来扩容服务器存储空间。这一期视频,我将以Linux为例,教大家如何进行扩容。群辉使用的是Linu...
- Linux 硬盘挂载(服务器重启自动挂载)
-
1、先查看目前机器上有几块硬盘,及已挂载磁盘:fdisk-l能够查看到当前主机上已连接上的磁盘,以及已经分割的磁盘分区。(下面以/dev/vdb磁盘进行分区、挂载为例,挂载点设置为/data)df...
- linux 挂载磁盘
-
在Linux中挂载硬盘的步骤如下:...
- 笨小猪教您Linux磁盘挂载
-
本教程针对Linux系统比较熟悉或者想学习Linux基础的用户朋友,本教程操作起来比较傻瓜式,跟着步骤就会操作,本文使用的工具是XShell同时多多注意空格(文中会有提示)。【问答】什么是磁盘挂载?答...
- Linux 磁盘挂载和docker安装命令
-
本篇给大家介绍Linux磁盘挂载和docker安装的相关内容,Linux服务器的操作是一个手熟的过程,一些不常用的命令隔断时间就忘记了,熟话说好记性不如烂笔头,还需在平时的工作中多练习记录。...
- Linux设置开机自动挂载分区
-
有时候,我们在安装完Linux系统之后,可能在使用过程中添加硬盘或者分区进行使用,这时候就需要手动把磁盘分区挂载到某个路径,但是开机之后就会消失,需要重新挂载,非常麻烦,那么我们应该如何设置开机自动挂...
- 在linux挂载一个新硬盘的完整步骤
-
以下是在Linux中挂载新原始磁盘的完整步骤,包括分区、创建文件系统以及使用UUID在/etc/fstab中启动时挂载磁盘:将新的原始磁盘连接到Linux系统并打开电源。运行以下命令,...
- Linux系统如何挂载exFAT分区
-
简介:Linux系统中不能像Windows系统那样自动识别加载新设备,需要手动识别,手动加载。Linux中一切皆文件。文件通过一个很大的文件树来组织,文件树的根目录是:/,从根目开始录逐级展开。这些文...
- Linux系统挂载硬盘
-
fdisk-l查看可挂载的磁盘都有哪些df-h查看已经挂载的磁盘...
- WSL2发布,如何在Win10中挂载Linux文件系统
-
WSL2是最新版本的架构,它为Windows子系统提供支持,使其能够在Windows上运行ELF64Linux二进制文件。通过最近的更新,它允许使用Linux文件系统访问存储在硬盘中的文件。如果你...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle忘记用户名密码 (59)
- oracle11gr2安装教程 (55)
- mybatis调用oracle存储过程 (67)
- oracle spool的用法 (57)
- oracle asm 磁盘管理 (67)
- 前端 设计模式 (64)
- 前端面试vue (56)
- linux格式化 (55)
- linux图形界面 (62)
- linux文件压缩 (75)
- Linux设置权限 (53)
- linux服务器配置 (62)
- mysql安装linux (71)
- linux启动命令 (59)
- 查看linux磁盘 (72)
- linux用户组 (74)
- linux多线程 (70)
- linux设备驱动 (53)
- linux自启动 (59)
- linux网络命令 (55)
- linux传文件 (60)
- linux打包文件 (58)
- linux查看数据库 (61)
- linux获取ip (64)
- 关闭防火墙linux (53)