linux应用编程笔记(16)多线程编程函数全解析

本文总结了多线程编程的基础,并详细介绍了多线程编程中的关键函数,包括线程创建、线程共享数据问题、终止线程、线程等待、线程标识和线程清除等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘要: 总结了多线程编程的基础,并对常用的多个函数进行了总结,每个函数配例子加深理解。


一、多线程基础

    线程技术很早就被创建,但是真正应用时间是在八十年代,较早的Unix中一个进程只允许有一个线程,现在多线程技术已经被运用到多个操作系统中,windows/nt,还有linux。

    引入线程的优势

    第一、和进程相比,线程是轻量级的,在linux下,一个进程被创建,系统需要分配资源给它,包括独立的地址空间,堆栈等等,建立数据表维护其代码段,堆栈和数据段,开销还是比较大的。而隶属于同一个进程中的多个线程,他们共用该进程所分配到的资源,彼此切换所需要的时间非常少,一个进程的开销大约是一个线程的三十倍。

    第二、线程之间的通信更加方便,进程因为拥有独立的数据段和代码段,他们之间的通信需要依赖进程间的通信方式,管道,socket,信号,信号量,共享内存和消息队列这些就是为此服务的。而隶属于同一个进程的多个线程是共享代码段和数据段的,因此他们共享数据,彼此之间通信非常简单,效率大大增强。

    第三、虽然线程的效率高,但是健壮性不如进程,因此一个程序中,用多少线程和进程,需要权衡。

    第四、线程作为一种多任务,并发的工作方式,使得多CPU系统运行更加高效,操作系统会保证当线程数不大于CPU数目时,在每个CPU上运行不同的线程。线程还可以改善程序结构,使一个冗长的进程拆分成几个独立或者半独立的线程,更加高效。

    linux下的线程支持POSIX线程接口,成为pthread,编写linux下的多线程程序,需要加入头文件pthread.h,如果需要连接,还要加上libpthread.a,也就是-lpthread。

二、多线程编程函数

1.创建线程

函数名pthread_create

函数原型int pthread_create(pthread_t *tidp,constpthread_attr_t *attr,void* (*start_rtn)(void),void *arg);

函数功能创建一个线程,并将线程要执行的函数传进去。

头文件#include <pthread.h>

返回值成功返回与0,失败返回出错编号

参数说明

       1.pthread_t *tdip:指向线程标识符的指针。

       2.const pthread_attr_t *attr:用来设置线程的属性。

       3.void* (*start_rtn)(void):用来设置线程要执行的函数的起始地址,这是一个void*类型的函数指针,之所以是void*类型,可以参考malloc这个函数,为了可以强制转换成任何我们需要执行的函数的类型。这里的参数看起来很复杂,其实使用起来是相当的简单。

       4.void *arg:传给前面函数的参数。

例子1:pthread_create

<span style="font-size:18px;">#include <stdio.h>
#include <pthread.h>
 
void *myThread1(void)
{
    int i;
    for(i=0;i<5;i++)
       {
           printf("thisis the first pthread!\n");
           sleep(2); 
       }  
}
 
void *myThread2(void)
{
    int i;
    for(i=0;i<5;i++)
       {
           printf("thisis the second pthread!\n");
           sleep(2); 
       }  
}
 
 
int main()
{
    int i=0,ret=0;
    pthread_t id1,id2;
    /*创建线程1*/
    ret=pthread_create(&id1,NULL,(void*)myThread1,NULL);
    if(ret)
       {
           printf("wecreat thread1 failed!\n");
           return -1;
       }
    /*创建线程2*/
    ret=pthread_create(&id2,NULL,(void*)myThread2,NULL);
    if(ret)
       {
           printf("wecreat thread1 failed!\n");
           return -1;
       }
      pthread_join(id1, NULL);
   pthread_join(id2, NULL);//线程退出函数   
      
       return 0; 
}
编译时候要加上-lpthread,运行结果如下:
this is the second pthread!
this is the first pthread!
this is the second pthread!
this is the first pthread!
this is the second pthread!
this is the first pthread!
this is the second pthread!
this is the first pthread!
this is the second pthread!
this is the first pthread!</span>

    这个例子演示了一个不传参数的线程创建方法,可以看到,两个线程并行运行,打印出了各自的消息。下面一个例子创建带参数传递的线程例子。

 

例子2:pthread_create带参数

<span style="font-size:18px;">#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
void *create(void *arg)
{
    int* num;
    num=(int*)arg;
    printf("createparamenter is:%d\n",*num);
    return (void*)0; 
}
 
 
int main(int argc,char *argv[])
{
    pthread_t tidp;
    int ret;
   
    int test=4;
    int *attr=&test;
   
    ret=pthread_create(&tidp,NULL,(void*)create,(void*)attr);
    if(ret)
       {
           printf("wecreate pthread1 error!\n");
           return-1;
       }
    sleep(1);
    printf("createpthread1 success!\n");
    return 0; 
}
编译的时候加上-lpthread,运行效果如下;
create paramenter is:4
create pthread1 success!</span>

例子3:pthread_share线程共享数据问题

<span style="font-size:18px;">#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
 
int a=1;//全局变量
 
void *create(void* arg)
{
    printf("newpthread ...\n");
    printf("a=:%d\n",a);
    a++;
   
    return (void*)0; 
}
 
int main(int argc,char *argv[])
{
   pthread_t tidp;
   int error;
 
   
   error=pthread_create(&tidp, NULL, create, NULL);
 
   if(error!=0)
    {
       printf("new thread is not create ... \n");
       return -1;
    }
   
   sleep(1);
   printf("in man ,a is:%d\n",a);
   
   return 0;
}
编译运行结果如下:
new pthread ...
a=:1
in man ,a is:2</span>

    可以看到,线程是共享进程的数据段的,这里有堆空间和栈空间优先分配哪一个部分的问题,这里在前面C语言的帖子里已经着重讲过了,不多做赘述。


2.终止线程

    如果进程中任意一个线程调用exit或者_exit,那么整个进程都会终止,线程的正常退出的方式有以下几种:

    (1)线程从启动函数中返回,使用return

    (2)线程可以被另一个进程终止

    (3)线程自己调用pthread_exit函数:

函数原型:void pthread_exit(void* rval_ptr)

功能:终止调用的线程。

参数void *rval_ptr:线程退出返回值的指针。

 

例子4:pthread_ret利用第一种方法,在线程函数中使用return返回。

<span style="font-size:18px;">#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
void *create(void *arg)
{
    int *num;
    num=(int*)arg;
    printf("createparamenter is:%d\n",*num);
    return (void*)8; 
}
 
 
int main(int argc,char *argv[])
{
    pthread_t tidp;
    int ret;
    int error;
    void* temp;
   
    int test=4;
    int *attr=&test;
   
    ret=pthread_create(&tidp,NULL,(void*)create,(void*)attr);
    if(ret)
       {
           printf("wecreate pthread1 error!\n");
           return -1;
       }
    error=pthread_join(tidp,&temp);
    printf("create pthread1 success!\n");
    printf("return value is:%d\n",(int)temp);
    return 0; 
}
编译运行后效果如下:
create paramenter is:4
create pthread1 success!
return value is:8</span>

    终止线程后返回的值打印出来了,这里的pthread_join函数下面会介绍。


3.线程等待

函数名pthread_join

函数原型int pthread_join(pthread_t *tid,void **rval_ptr);

函数功能阻塞调用线程,直到指定的线程运行终止,回收其资源,如果线程在调用前已经结束,该函数立即返回。指定的线程要是joinable的。

头文件#include <pthread.h>

返回值成功返回与0,失败返回出错编号

参数说明

       1.pthread_t *tid:线程标识符,线程ID,标识唯一线程。

       2.void **rval_ptr:用户定义的指针,用来存储被等待线程的返回值。

 

例子5:pthread_join,使用pthread_join的效果

<span style="font-size:18px;">#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
 
void *thread(void *str)
{
    int i;
    for(i=0;i<4;i++)
    {
       sleep(2);
       printf("thisis in thread:%d\n",i);   
    }  
   
    return NULL;
   
}
 
int main(void)
{
    pthread_tpth;
    int i;
    int ret;
   
    ret=pthread_create(&pth,NULL,thread,(void*)i);
   
    pthread_join(pth,NULL);
   
    printf("**********\n");
    for(i = 0; i < 4; ++i)
    {
       sleep(1);
       printf( "This in the main : %d\n" , i );
    }
   
    return 0;
}
编译运行效果如下:
this is in thread:0
this is in thread:1
this is in thread:2
this is in thread:3
**********
This in the main : 0
This in the main : 1
This in the main : 2
This in the main : 3</span>

    可以看到,调用的进程在线程退出后才开始执行,如果不调用,会交替输出。


4.线程标识

函数名:pthread_self

函数原型:pthread_t pthread_self(void)

函数功能:获取调用线程的id。

返回值:返回调用线程的id。

参数说明:无参数。


例子6:pthread_self

<span style="font-size:18px;">#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
void *create(void *arg)
{
    printf("new thread...\n");
    printf("the thread id is:%d\n",(unsigned int)pthread_self());
    printf("the process id is:%d\n",getpid());   
   
    return (void*)0;
}
 
int main(int argc,char* argv[])
{
    pthread_t tid;
    int error;
    printf("mainthread is starting...\n");
   
    error=pthread_create(&tid,NULL,create,NULL);
    if(error)
       {
           printf("wecreate thread failed!\n");
           return -1;
       }  
    printf("themain pid is:%d\n",getpid());
    sleep(1);
      
    return 0; 
}
编译运行后效果如下:
main thread is starting...
the main pid is:5002
new thread...
the thread id is:-1215562896
the process id is:5002</span>

    可以看到,获取到了线程的id,还有线程中返回的进程的pid,两个pid是一样的,说明该线程是隶属于这个进程的,也可以看到线程的标识位数是很大的,进程就不那么大,那是因为线程的开销小得多,可以创建很多个线程,进程则需眼分配资源。

 

5.线程清除

    线程不是一直都能够正确的执行,也不是都能正确的退出,在正常情况下,线程可以通过pthread_exit或者在线程使用return正常退出,这都是可预见的退出方式;在其他线程的干预下,或者由于自身运行出错(例如访问非法地址等)而退出,这种退出方式是不可预见的。

    以上无论哪种情况,由于线程也是占用资源,所以存在资源释放的问题,保证线程在终止的时候,正确的释放所占用的资源,就是我们这里要介绍的。

    从pthread_cleanup_push的调用点到pthread_cleanup_pop之间的程序段中的终止动作,(包括异常和调用pthread_exit(),但是不包括return),都将会执行pthread_cleanup_push()所指定的清理函数。


压栈函数(先进后出)

函数名:pthread_cleanup_push

函数原型:void pthread_cleanup_push(void (*rtn)(void *),void *arg)

功能:将指定的清楚函数压入栈

参数说明

       1. void (*rtn)(void *):指定的清理函数。

       2. void *arg:清除函数的参数。


出栈函数

函数名:pthread_cleanup_pop

函数原型:void pthread_cleanup_pop(int execute)

功能:将清除函数弹出清除栈

参数说明:0,在弹出的时候不执行;1,在弹出的时候执行。


例子7:pthread_clean

<span style="font-size:18px;">#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *clean(void *arg)
{
   printf("cleanup :%s \n",(char *)arg);
   return (void *)0;
}
void *thr_fn1(void *arg)
{
   printf("thread 1 start \n");
   pthread_cleanup_push( (void*)clean,"thread 1 first handler");
   pthread_cleanup_push( (void*)clean,"thread 1 second hadler");
   printf("thread 1 push complete \n");
   if(arg)
    {
       return((void *)1);//这里是return退出的,所以不会调用pop压入栈清理函数
    }
   pthread_cleanup_pop(0);
   pthread_cleanup_pop(0);
   return (void *)1;
}
 
 
void *thr_fn2(void *arg)
{
   printf("thread 2 start \n");
   pthread_cleanup_push( (void*)clean,"thread 2 first handler");
   pthread_cleanup_push( (void*)clean,"thread 2 second handler");
   printf("thread 2 push complete \n");
   if(arg)
    {
       pthread_exit((void *)2);//exit调用会触发,由于是压栈方式,所以这里先输出second,然后是first
    }
   pthread_cleanup_pop(0);
   pthread_cleanup_pop(0);
   pthread_exit((void *)2);
}
 
 
 
int main(void)
{
   int err;
   pthread_t tid1,tid2;//两个线程的id
   void *tret;//返回的参数
 
   err=pthread_create(&tid1,NULL,thr_fn1,(void *)1);//创建线程1并判断是否成功
   
   if(err!=0)
    {
       printf("error .... \n");
       return -1;
    }
   err=pthread_create(&tid2,NULL,thr_fn2,(void *)1);//创建线程2并判断是否成功
 
   if(err!=0)
    {
       printf("error .... \n");
       return -1;
    }
   err=pthread_join(tid1,&tret);//等待线程1执行结束,并判断是否正确退出
   if(err!=0)
    {
       printf("error .... \n");
       return -1;
    }
   printf("thread 1 exit code %d \n",(int)tret);
 
   err=pthread_join(tid2,&tret);//等待线程2执行结束,并判断是否正确退出
   if(err!=0)
    {
       printf("error .... ");
       return -1;
    }
 
   printf("thread 2 exit code %d \n",(int)tret);
   
   return 1;
}
编译运行效果如下;
thread 2 start 
thread 2 push complete 
cleanup :thread 2 second handler 
cleanup :thread 2 first handler 
thread 1 start 
thread 1 push complete 
thread 1 exit code 1  
thread 2 exit code 2 </span>

    这篇帖子就总结到这里吧,如有不正确的地方还请指出,大家共同进步!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值