Linux中线程使用详解 您所在的位置:网站首页 同一个进程的多个线程堆栈怎么设置 Linux中线程使用详解

Linux中线程使用详解

2024-07-13 22:56| 来源: 网络整理| 查看: 265

线程与进程为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。

  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:

  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。

  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

 

一、线程标识

线程有ID, 但不是系统唯一, 而是进程环境中唯一有效. 线程的句柄是pthread_t类型, 该类型不能作为整数处理, 而是一个结构.

下面介绍两个函数:

头文件: 原型: int pthread_equal(pthread_t tid1, pthread_t tid2); 返回值: 相等返回非0, 不相等返回0. 说明: 比较两个线程ID是否相等.

 

头文件: 原型: pthread_t pthread_self(); 返回值: 返回调用线程的线程ID.

二、线程创建

 在执行中创建一个线程, 可以为该线程分配它需要做的工作(线程执行函数), 该线程共享进程的资源. 创建线程的函数pthread_create()

头文件: 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg); 返回值: 成功则返回0, 否则返回错误编号. 参数: tidp: 指向新创建线程ID的变量, 作为函数的输出. attr: 用于定制各种不同的线程属性, NULL为默认属性(见下). start_rtn: 函数指针, 为线程开始执行的函数名.该函数可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由 pthread_join()获取 arg: 函数的唯一无类型(void)指针参数, 如要传多个参数, 可以用结构封装.

linux下多线程程序的编译方法:

       因为pthread的库不是linux系统的库,所以在进行编译的时候要加上     -lpthread

       # gcc filename -lpthread  //默认情况下gcc使用c库,要使用额外的库要这样选择使用的库

 

例1:thread_create.c

1 #include 2 #include //包线程要包含 3 void *mythread1(void) 4 { 5 int i; 6 for(i=0;i#include 2 #include 3 #include 4 5 void cleanup(void *arg) 6 { 7 printf("cleanup: %s\n", (char *)arg); 8 } 9 10 void *thr_fn1(void *arg) 11 { 12 printf("thread 1 start\n"); 13 pthread_cleanup_push(cleanup, "thread 1 first handler"); 14 pthread_cleanup_push(cleanup, "thread 1 second handler"); 15 printf("thread 1 push complete\n"); 16 if (arg) 17 return((void *)1); 18 // pthread_exit((void *)2); 19 20 pthread_cleanup_pop(0); 21 pthread_cleanup_pop(0); 22 // return((void *)1); 23 pthread_exit((void *)2); 24 25 } 26 27 void *thr_fn2(void *arg) 28 { 29 printf("thread 2 start\n"); 30 pthread_cleanup_push(cleanup, "thread 2 first handler"); 31 pthread_cleanup_push(cleanup, "thread 2 second handler"); 32 printf("thread 2 push complete\n"); 33 if (arg) 34 pthread_exit((void *)2); 35 pthread_cleanup_pop(0); 36 pthread_cleanup_pop(0); 37 pthread_exit((void *)2); 38 } 39 40 int main(void) 41 { 42 int err; 43 pthread_t tid1, tid2; 44 void *tret; 45 46 err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); 47 if (err != 0) 48 printf("can't create thread 1: %c\n", strerror(err)); 49 err = pthread_create(&tid2, NULL, thr_fn2, (void *)1); 50 if (err != 0) 51 printf("can't create thread 2: %c\n", strerror(err)); 52 err = pthread_join(tid1, &tret); 53 if (err != 0) 54 printf("can't join with thread 1: %c\n", strerror(err)); 55 printf("thread 1 exit code %d\n", (int)tret); 56 err = pthread_join(tid2, &tret); 57 if (err != 0) 58 printf("can't join with thread 2: %c\n", strerror(err)); 59 printf("thread 2 exit code %d\n", (int)tret); 60 exit(0); 61 } 62

 

pthread_detach()函数:

 

创建一个线程默认的状态是joinable。

如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码).

所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代 码,回收其资源(类似于wait,waitpid) 。

但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。

比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以

1)在子线程中加入代码pthread_detach(pthread_self())

2)父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)   

这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

 

pthread_kill()函数:

 

pthread_kill与kill有区别,是向线程发送signal。,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数。

int pthread_kill(pthread_t thread, int sig);

向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT,但线程却没有实现signal处理函数,则整个进程退出。

pthread_kill(threadid, SIGKILL)杀死整个进程。 如果要获得正确的行为,就需要在线程内实现signal(SIGKILL,sig_handler)。所以,如果int sig的参数不是0,那一定要清楚到底要干什么,而且一定要实现线程的信号处理函数,否则,就会影响整个进程。

如果int sig是0呢,这是一个保留信号,一个作用是用来判断线程是不是还活着。pthread_kill的返回值: 成功:0 线程不存在:ESRCH 信号不合法:EINVAL

代码:

复制代码 int kill_rc = pthread_kill(thread_id,0); if(kill_rc == ESRCH) printf("the specified thread did not exists or already quit\n"); else if(kill_rc == EINVAL) printf("signal is invalid\n"); else printf("the specified thread is alive\n"); 复制代码

 

这里附上线程基本函数:

------------------------------------------------------------------------------------------                      POSIX函数                                                    描述 -------------------------------------------------------------------------------------------                     pthread_create                                    创建一个线程                     pthread_self                                        找出自己的线程ID                     pthread_equal                                     测试2个线程ID是否相等                     pthread_detach                                   设置线程以释放资源                     pthread_join                                        等待一个线程                     pthread_cancel                                    终止另一个线程                     pthread_exit                                        退出线程,而不退出进程                     pthread_kill                                         向线程发送一个信号 -------------------------------------------------------------------------------------------       线程属性pthread_attr_t简介 Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。在pthread_create中,把第二个参数设置为NULL的话,将采用默认的属性配置。 pthread_attr_t的主要属性的意义如下: __detachstate,表示新线程是否与进程中其他线程脱离同步, 如果设置为PTHREAD_CREATE_DETACHED 则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。 __schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。 __schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。 __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。 __scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。   为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_getXXX/pthread_attr_setXXX函数。 在设置线程属性 pthread_attr_t 之前,通常先调用pthread_attr_init来初始化,之后来调用相应的属性设置函数。 主要的函数如下: 1、pthread_attr_init 功能:        对线程属性变量的初始化。 头文件:     函数原型:   int pthread_attr_init (pthread_attr_t* attr); 函数传入值:attr:线程属性。 函数返回值:成功: 0                 失败: -1 2、pthread_attr_setscope 功能:        设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。默认为PTHREAD_SCOPE_PROCESS。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。 头文件:     函数原型:   int pthread_attr_setscope (pthread_attr_t* attr, int scope); 函数传入值:attr: 线程属性。                       scope:PTHREAD_SCOPE_SYSTEM,表示与系统中所有线程一起竞争CPU时间,                                  PTHREAD_SCOPE_PROCESS,表示仅与同进程中的线程竞争CPU 函数返回值得:同1。 3、pthread_attr_setdetachstate 功能:        设置线程detachstate属性。该表示新线程是否与进程中其他线程脱离同步,如果设置为PTHREAD_CREATE_DETACHED则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。 头文件:       函数原型:    int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate); 函数传入值:attr:线程属性。 detachstate:PTHREAD_CREATE_DETACHED,不能用pthread_join()来同步,且在退出时自行释放所占用的资源                     PTHREAD_CREATE_JOINABLE,能用pthread_join()来同步 函数返回值得:同1。 4、pthread_attr_setschedparam 功能:       设置线程schedparam属性,即调用的优先级。 头文件:     函数原型:   int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param); 函数传入值:attr:线程属性。                  param:线程优先级。一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0 函数返回值:同1。 5、pthread_attr_getschedparam 功能:       得到线程优先级。 头文件:     函数原型:  int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param); 函数传入值:attr:线程属性;                     param:线程优先级; 函数返回值:同1。   例: 1 #include 2 #include 3 #include 4 #include 5 static void pthread_func_1 (void); 6 static void pthread_func_2 (void); 7 8 int main (int argc, char** argv) 9 { 10 pthread_t pt_1 = 0; 11 pthread_t pt_2 = 0; 12 pthread_attr_t atrr = {0}; 13 int ret = 0; 14 15 /*初始化属性线程属性*/ 16 pthread_attr_init (&attr); 17 pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); 18 pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); 19 20 ret = pthread_create (&pt_1, &attr, pthread_func_1, NULL); 21 if (ret != 0) 22 { 23 perror ("pthread_1_create"); 24 } 25 26 ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL); 27 if (ret != 0) 28 { 29 perror ("pthread_2_create"); 30 } 31 32 pthread_join (pt_2, NULL); 33 34 return 0; 35 } 36 37 static void pthread_func_1 (void) 38 { 39 int i = 0; 40 41 for (; i < 6; i++) 42 { 43 printf ("This is pthread_1.\n"); 44 45 if (i == 2) 46 { 47 pthread_exit (0); 48 } 49 } 50 51 return; 52 } 53 54 static void pthread_func_2 (void) 55 { 56 int i = 0; 57 58 for (; i < 3; i ++) 59 { 60 printf ("This is pthread_2.\n"); 61 } 62 63 return; 64 }

 

   


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有