虽然转做linux底层开发了,但上层的开发知识还是不能忘记的,写测试程序的时候还是会用到的。这里就复习下线程知识吧。
头文件
只要代码中包含了有关线程的数据结构或者相关函数都需要包含线程头文件: #include<pthread.h>
编译
然后是编译问题,编译有关线程的代码时,都需要加一个选项 -lpthread;如:gcc -o xxx xxx -lpthread;如果出现下面类似的错误:
/tmp/ccc9R9dJ.o: In function `main':
test.c:(.text+0x3c): undefined reference to `pthread_create'
collect2: ld returned 1 exit status
就很可能是因为编译时没有加-lpthread 选项;
线程比较
因为线程id是pthread_t 类型,这个pthread_t 在不同系统结构中有不同的类型,有int型的,也有unsigned int型的,还有unsigned long型的。所以比较的时候不能简单的 == 这么容易,而是需要用专门的函数来进行比较。
pthread_equal(pthread_t t1, pthread_t t2);如果线程t1和线程t2的id相等,则返回非0;否则,返回0;
线程ID获取
和进程一样线程也有获取自身ID的函数,这个函数一般用来打印线程ID的,还有就是和上面的线程比较一起使用,获取到线程ID然后和指定线程ID比较,如果相等的话,那就知道正在执行该代码的是哪个线程了。
pthread_self(void);该函数返回调用线程的线程ID;
线程的创建
一般来说每个进程中都默认包含一个主线程,当然如果还需要处理其他任务的话,那么就可以再创建个线程。如果又有新任务的话,还可以继续创建新线程去完成。这和传统的进程创建基本类似。
pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void), void *arg);
参数1 ==> pthread_t *tid:是创建的新线程id。这是个返回参数,比如:定义个线程id: pthread_t t1;调用时 pthread_create(&t1, xxx, xxx, xxx);调用完以后t1中就包含了新线程的ID了。
参数2 ==> const pthread_attr_t *attr:线程属性。这个属性要经过设置后再传递给创建函数。后面会具体分析;
参数3 ==> void* (*func)(void):线程处理函数。一般这么定义: void* func(void *arg);新线程会马上进入该函数去执行。
参数4 ==> void *arg:这是传递给线程处理函数的参数。这里要强制转换下。
返回值:若成功,则返回0;失败则返回错误码 errno;
下面看看简单实例:
#include<stdio.h>
#include<pthread.h>
void* test(void* arg)
{
int *i = (int*)arg;
printf("arg:%d\n", *i);
return (void*)0;
}
int main(void)
{
pthread_t t1;
int arg = 100;
pthread_create(&t1, NULL, &test, (void*)(&arg));
pause();
return 0;
}
线程的终止
在讲线程终止时有必要说下线程和进程关系。一般会说线程是轻量级的,而进程是重量级的。为什么这么说呢?因为线程是运行(工作)的最小单位,而进程是资源的最小单位。也就是说分配一个进程要同时分配他一些资源,比如说内存之类的,那当然要做些初始化,内存申请等等工作,所以启动一个进程是比较耗费时间的。而线程不一样,同一进程中的所有线程都会共享进程中的资源(线程也可以设置堆栈大小,但都是在进程中分配的,而不是内存),所以分配一个线程是分分钟钟的事。这也是一般用多线程而不用多进程的原因吧。
讲了线程和进程的关系,那么说下进程、线程、信号之间的关系。信号是发给进程的,就是信号的对象单位是进程而不是线程。但是进程收到信号后会让线程抢占,谁抢赢了谁就执行信号的响应函数,并且信号只在进程中执行一次,也就是说一个线程执行完后,该信号就消失了,其他线程不能执行了。信号的响应函数影响的单位为进程,而不是线程。
比如:进程A中有1、2、3个线程,当进程A中接受到一个9号信号,进程A会让3个线程来抢这个资源(信号也是资源),假设线程2 抢到了该信号,则线程2会执行9号信号对应的处理函数。9号信号默认是杀死进程,那么进程A就会被杀死掉。
上面两点面试的时候都有问到过,所以新人面试时要多注意些。
好,会到主题上来。通过上面的分析可以知道信号是着用于进程,但是由进程中的线程去执行,执行的结果会影响到整个进程。所以线程终止不能调用exit、_exit、_EXIT来退出,因为这样会让整个进程死掉。线程通过下面的方法退出:
1、调用完处理函数return返回 === 自然死亡;
2、被同一进程中的其他线程取消 === 被谋杀;
3、调用pthread_exit() === 自杀;
自然死亡已经讲过(线程的创建),现在来分析下自杀;
pthread_exit(void **arg); 成功返回0,失败返回错误码;参数:就相当于自杀时的遗言,留下了她最后的一句话。
#include<stdio.h>
#include<pthread.h>
void* test1(void* arg)
{
printf("in test1\n");
pthread_exit((void*)100);
return (void*)0;
}
int main(void)
{
pthread_t t1;
void *arg;
pthread_create(&t1, NULL, &test1, NULL);
pthread_join(t1, &arg);
printf("t1 arg:%d\n", (int)arg);
pause();
return 0;
}
还有个函数是上面提到的被谋杀,pthread_cancel(pthread_t t1);这个不好写小实验,因为pthread_cancel(t1)仅仅是告诉她:你去死吧。而真正死不死还是看她自己。
线程清理处理程序
进程中可以用atexit()函数来做收尾清理函数,线程也有一对宏函数做收尾的清理处理程序。
pthread_cleanup_push(void (*func) (void*), void *arg);
pthread_cleanup_pop(int execute);
pthread_cleanup_push(void (*func)(void*), void *arg)函数中地一个参数:func为注册函数, void func(void *);第二个参数:void *arg为地一个参数func注册函数的参数,将会传入到func函数中。
pthread_cleanup_pop(int execute);函数中execute若为0,则不掉用注册函数。
记住这是一对宏函数,必须要成对出现的。
#include<stdio.h>
#include<pthread.h>
void* fun1(void *arg)
{
printf("fun1():%d\n", (int)arg);
return (void*)0;
}
void* fun2(void *arg)
{
printf("fun2():%d\n", (int)arg);
return (void*)0;
}
void* test1(void* arg)
{
pthread_cleanup_push(&fun1, (void*)1);
pthread_cleanup_push(&fun2, (void*)2);
pthread_cleanup_pop(1);
printf("in test1\n");
pthread_cleanup_pop(1);
return (void*)0;
}
int main(void)
{
void *arg;
pthread_t t1;
pthread_create(&t1, NULL, &test1, NULL);
pause();
return 0;
}
转载地址:http://blog.csdn.net/yuzhihui_no1/article/details/46494911