Linux多线程与信号

Linux多线程与信号

http://www.fuzhijie.me/?p=87

By 绚丽也尘埃

Linux下的线程是所谓的轻量级进程(LWP: light weight process),其与普通进程一样拥有一个庞大的task_struct结构体,一个进程中的多个线程共享内存空间,毕竟它们属于同一个进程,所以需要向外呈现一个统一的pid,因此各线程的pid存放的是进程号,又由于线程同样也是进程,因此其有自己的进程id号,Linux为了支持多线程,于是添加了一个tid字段用于存放本线程的进程号,线程组的主线程pid同tid一致。getpid系统调用用于获取本进程的进程号,那怎么获取本线程(所谓的轻量级进程)的进程号呢?我刚从网上学到了一个技巧,据说是由于glibc没有包装该系统调用,所以只好自己手动使用syscall去调用了。gettid的系统调用号保存在”/usr/include/asm/unistd_32.h”头文件中,打开该文件可以看到有如下内容:

?
1
2
3
/* 223 is unused */
#define __NR_gettid  224
#define __NR_readahead  225

因此可以使用如下方式来实现gettid函数:

?
1
2
3
4
5
pid_t gettid()
{
//直接使用224代替SYS_gettid也可以
return syscall(SYS_gettid);
}

下面这个程序较好地演示了多线程环境下使用信号的特点。线程的标识为pthread_t,它是一个结构体,直接打印会是一个地址,因此可以使用上面的gettid函数来获取本线程的进程号。

?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
sleep函数:
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
此函数使调用进程被挂起,直到满足以下条件之一:
1)已经过了seconds所指定的墙上时钟时间
2)调用进程捕捉到一个信号并从信号处理程序返回
注:由于其他系统活动,实际返回时间比所要求的会迟一些,像alarm一样。
sleep的返回值:
1)在上述第一种情形中,返回值是0
2)当由于捕捉到某个信号sleep提前返回时,返回值是未睡够的时间(所要求的时间减去实际休眠时间)
*/
  
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/syscall.h>
  
#include <linux/unistd.h>
  
#define NUMTHREADS 3
  
//获取线程的pid
pid_t gettid()
{
     return syscall(SYS_gettid);
}
  
void sighand( int signo);
  
//普通线程组的执行函数
void *threadfunc( void *parm)
{
     pthread_t  tid = pthread_self();
  
     int rc;
  
     printf ( "Thread %d entered\n" , gettid());
  
     rc = sleep(10);
  
     //需要仔细观察sleep函数的返回值
     printf ( "Thread %d did not get expected results! rc=%d\n" , gettid(), rc);
  
     return NULL;
}
  
//屏蔽所有信号的线程组的执行函数
void *threadmasked( void *parm)
{
     pthread_t tid = pthread_self();
  
     sigset_t mask;
  
     int rc;
  
     printf ( "Masked thread %d entered\n" , gettid());
  
     sigfillset(&mask);
  
     //这组线程阻塞所有的信号
     rc = pthread_sigmask(SIG_BLOCK, &mask, NULL);
  
     if (rc != 0)
     {
         printf ( "%d, %s\n" , rc, strerror (rc));
  
         return NULL;
     }
  
     rc = sleep(5);
  
     //所以它们可以安心地睡到自然醒
     //如果rc不能0就不正常了~
     if (rc != 0)
     {
         printf ( "Masked thread %d did not get expected results! " "rc=%d \n" , gettid(), rc);
  
         return NULL;
     }
  
     printf ( "Masked thread %d completed masked work\n" , gettid());
  
     return NULL;
}
  
int main( int argc, char **argv)
{
     int rc;
  
     int i;
  
     struct sigaction actions;
  
     //两组线程
     pthread_t threads[NUMTHREADS];
  
     pthread_t maskedthreads[NUMTHREADS];
  
     printf ( "Enter Testcase - %s\n" , argv[0]);
  
     printf ( "Set up the alarm handler for the process\n" );
  
     memset (&actions, 0, sizeof (actions));
  
     sigemptyset(&actions.sa_mask);
  
     actions.sa_flags = 0;
  
     //信号处理函数
     actions.sa_handler = sighand;
  
     //给定时器事件注册处理器
     //如果不捕捉该信号,该信号会对整个进程起作用
     //Linux下收到SIGALRM信号不处理,默认会打印"提醒时钟"
     //然后进程就退出了
     rc = sigaction(SIGALRM, &actions, NULL);
  
     printf ( "Create masked and unmasked threads\n" );
  
     //开启两组线程
     for (i=0; i < NUMTHREADS; ++i)
     {
         rc = pthread_create(&threads[i], NULL, threadfunc, NULL);
  
         if (rc != 0)
         {
             printf ( "%d, %s\n" , rc, strerror (rc));
  
             return -1;
         }
  
         rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL);
  
         if (rc != 0)
         {
             printf ( "%d, %s\n" , rc, strerror (rc));
  
             return -1;
         }
     }
  
     //睡眠3秒再发信号
     sleep(3);
  
     printf ( "Send a signal to masked and unmasked threads\n" );
  
     //向两组线程发送信号
     for (i=0; i < NUMTHREADS; ++i)
     {
         rc = pthread_kill(threads[i], SIGALRM);
  
         rc = pthread_kill(maskedthreads[i], SIGALRM);
     }
  
     printf ( "Wait for masked and unmasked threads to complete\n" );
  
     //等待子进程退出
     sleep(15);
  
     printf ( "Main completed\n" );
  
     return 0;
}
  
//SIGALRM信号处理函数
void sighand( int signo)
{
     printf ( "Thread %d in signal handler\n" , gettid());
  
     return ;
}

程序运行的结果如下:

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
ecy@ecy-geek:~/pthreads$ ./pthread_mask
Enter Testcase - ./pthread_mask
Set up the alarm handler for the process
Create masked and unmasked threads
Thread 11145 entered
Masked thread 11146 entered
Thread 11147 entered
Masked thread 11148 entered
Thread 11149 entered
Masked thread 11150 entered
Send a signal to masked and unmasked threads
Wait for masked and unmasked threads to complete
Thread 11149 in signal handler
Thread 11149 did not get expected results! rc=7
Thread 11145 in signal handler
Thread 11145 did not get expected results! rc=7
Thread 11147 in signal handler
Thread 11147 did not get expected results! rc=7
Masked thread 11146 completed masked work
Masked thread 11148 completed masked work
Masked thread 11150 completed masked work
Main completed

分析上述结果可知,在主线程设置好对某个信号的处理方法,同样会适用于子线程。上面的程序中,主线程设置好了SIGALRM信号处理函数,子线程收到SIGALRM信号会调用信号处理函数sighand来处理该信号(当然是在没有修改的情况下);使用pthread_kill可以向指定的线程发送信号;每个线程都可以有自己的信号屏蔽表,如果不屏蔽SIGALRM信号,SIGALRM会中断sleep的执行,sleep函数返回剩余的秒数。

你可能感兴趣的:(Linux多线程与信号)