Linux上线程开发API概要
多线程开发在Linux平台上已经有成熟的 pthread 库支持 (第三方库)。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中:
- 线程操作又分线程的创建,退出,等待3种。
- 互斥锁则包括4种操作,分别是创建,销毁,加锁和解锁。
- 条件操作有5种操作:创建,销毁,触发,广播和等待。
其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
线程的创建等待及退出
1. 线程的创建:
#include <pthread.h>
int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void *(*start_rtn)(void *), void * arg);
返回 : 若成功返回0,否则返回错误编号且*thread中的内容是未定义的。
参数 : thread 为指向线程标识符的指针。
attr 用来设置线程属性。默认属性为 NULL
*(*start_rtn) 线程运行函数的起始地址。
arg (*start_rtn) 函数的参数。
当成功时,thread 所指向的内存单元被设置为新线程的 id ,attr 参数用于定制各种不同的线程属性,可设为 NULL,以创建默认属性的线程。
新创建的线程从 start_rtn 函数地址开始运行,该函数只有一个无类型指针,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
Linux下用C语言开发多线程程序,Linux系统下的多线程遵循POSIX线程接口,称为pthread。
关于 pthread_t
typedef unsigned long int pthread_t;
//come from /usr/include/bits/pthreadtypes.h
用途:pthread_t用于声明线程ID。
pthread并非Linux系统的默认库,而是POSIX线程库。在Linux中将其作为一个库来使用,因此加上 -lpthread(或-pthread)以显式链接该库。函数在执行错误时的错误信息将作为返回值返回,并不修改系统全局变量errno,也无法使用perror()打印错误信息。
2. 线程的退出
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消。
3)线程调用pthread_exit:
#include <pthread.h>
int pthread_exit(void *rval_ptr);
线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。
这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。
3. 线程的等待
int pthread_join(pthread_t thread,void **rval_ptr);
pthread_join()函数,以阻塞的方式等待 thread 指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 : thread: 线程标识符,即线程ID,标识唯一线程。
retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值: 0代表成功。 失败,返回的则是错误号。
一个线程不能被多个线程等待,也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返回ESRCH 错误。
其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统调用copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。 copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在copy后的进程中便能体现出来。
线程的脱离,暂且不议
5. 线程ID获取和比较
ID的获取
#include <pthread.h>
pthread_t pthread_self(void);
返回:调用线程自身的ID
线程的比较,因为考虑到可移植性,我们不能把简单的线程ID当做整数来处理,
因为不同的系统对线程的ID定义有可能不同,所以使用了如下函数:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
比较线程 tid1 和 tide 是否为同一线程。
返回:若相等则返回非0值,否则返回 0
下面我们来写代码,验证
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
void * funcl(void * arg){
static int ret = 10; // static 静态全局变量
static char * p = "t1 is a run out";
printf("this is pthread t1, t1 的 ID 为 : %ld \n",pthread_self());
//errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定
义。查看错误代码errno是调试程序的一个重要方法。当linux C api函数发生异常时,一般>会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看
该值推测出错的原因。在实际编程中用这一招解决了不少原本看来莫名其妙的问题。
printf("t1's 参数为 %d \n",*(int *)arg);
printf("while 1 结束\n");
pthread_exit((void *)p);
}
int main(){
int ret;
pthread_t t1;
int * arg;
*arg = 30;
ret = pthread_create(&t1,NULL,funcl,(void *)arg);
if(ret == 0 ){
printf("pthread creat is success \n");
}
else {
printf("pthread_creat is fail \n");
printf("erron = %d \n",errno);
perror("is: ");
}
printf("this is pthread t0, t0 的 ID 为 : %ld \n",pthread_self());
// while(1); // 当主线程一旦退出以后,那么地址空间就会释放,此
时子进程就没有了。
// pthread_join(t1,NULL); // while(1); 有点low 啊,我们可以>使用 pthread_join(
// 我们先写一个 NULL 不回收 // 以阻塞的方式等待 thread 指定的线程结束
char * pret;
pthread_join(t1,(void **)&pret);
printf("main : t1 quit : %s\n",(char *)pret);
return 0;
}
我们之前说,线程是共享地址的,现在我们来康康,线程如何共享地址。
简单的验证一下
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
// int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
void * funcl2(void * arg){
static int ret = 10; // static 静态全局变量
static char * p = "t2 is a run out";
printf("this is pthread t2, t2 的 ID 为 : %ld \n",pthread_self());
//errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定
printf("t2's 参数为 %d \n",*(int *)arg);
pthread_exit((void *)p);
}
void * funcl1(void * arg){
static int ret = 10; // static 静态全局变量
static char * p = "t1 is a run out";
printf("this is pthread t1, t1 的 ID 为 : %ld \n",pthread_self());
//errno 是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定
义。查看错误代码errno是调试程序的一个重要方法。当linux C api函数发生异常时,一般>会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看
该值推测出错的原因。在实际编程中用这一招解决了不少原本看来莫名其妙的问题。
printf("t1's 参数为 %d \n",*(int *)arg);
printf("while 1 结束\n");
pthread_exit((void *)p);
}
int main(){
int ret1;
int ret2;
pthread_t t1;
pthread_t t2;
int * arg;
*arg = 30;
ret1 = pthread_create(&t1,NULL,funcl1,(void *)arg);
if(ret1 == 0){
printf("pthread creat is success \n");
}
else {
printf("pthread_creat is fail \n");
printf("erron = %d \n",errno);
perror("is: ");
}
ret2 = pthread_create(&t2,NULL,funcl2,(void *)arg);
printf("this is pthread t0, t0 的 ID 为 : %ld \n",pthread_self());
// while(1); // 当主线程一旦退出以后,那么地址空间就会释放,此
时子进程就没有了。
// pth read_join(t1,NULL); // while(1); 有点low 啊,我们可以
使用 pthread_join(
// 我们先写一个 NULL 不回收 // 以阻塞的方式等待 thread 指定的线程结束
char * pret;
pthread_join(t1,(void **)&pret);
printf("main : t1 quit : %s\n",(char *)pret);
pthread_join(t2,(void **)&pret);
printf("main : t2 quit : %s\n",(char *)pret);
return 0;
}