Posix多线程编程(1)—线程基础

来源:互联网 发布:最优化理论与方法 pdf 编辑:程序博客网 时间:2024/04/30 00:52

一.什么是线程
在一个程序里的多个执行路线就叫做线程。更准确的定义是:线程是一个进程内部的一个控制序列

典型的unix进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程以后,在程序设计时可以把进程设计成在同一时刻能够做不止一件事,每个线程处理各自独立的任务。


二.线程的优点

1)通过为每种事件类型的处理分配单独的线程,能够简化处理异步时间的代码。

2)多个线程可以自动共享相同的存储地址空间和文件描述符。

3)有些问题可以通过将其分解从而改善整个程序的吞吐量。

4 交互的程序可以通过使用多线程实现相应时间的改善,多线程可以把程序中处理用户输入输出的部分与其它部分分开。


三.线程的缺点

线程也有不足之处。编写多线程程序需要更全面更深入的思考。在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的。调试一个多线程程序也比调试一个单线程程序困难得多。


四.线程的结构

线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值、栈、调度优先级和策略、信号屏蔽子,errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存、栈以及文件描述符。


五.线程标识

就像每个进程有一个进程ID一样,每个线程也有一个线程ID,进程ID在整个系统中是唯一的,但线程不同,线程ID只在它所属的进程环境中有效。线程IDpthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread_t数据类型,所以可以移植的操作系统不能把它作为整数处理。因此必须使用函数来对来对两个线程ID进行比较。

函数1

名称:
pthread_equal
功能: 比较两个线程
ID
头文件:
#include <pthread.h>
函数原形:
int pthread_equal(pthread_t tid1,pthread_t tid2);
参数: tid1 进程1id tid2 进程
2id
返回值: 若相等返回非0值,否则返回
0
函数2

名称:
pthread_self
功能: 获取自身线程的
id
头文件:
#include <pthread.h>
函数原形:
pthread_t pthread_self(void);
参数:

返回值: 调用线程的线程
id
六.线程的创建

函数3

名称:
pthread_create
功能: 创建线程

头文件:
#include <pthread.h>
函数原形:
int pthread_create(pthread_t *restrict tidp,const pthread _attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg);
参数:

返回值: 若成功返回则返回0,否则返回错误编号


pthread_creat成功返回时, tidp指向的内存单元被设置为新创建线程的线程IDattr参数用于定制各种不同的线程属性。可以把它设置为NULL,创建默认的线程属性。

新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。


#include <pthread.h>

#include <stdio.h>
void printids(const char *s)
{
printf(“%s pid:%u  tid:%u /n“, getpid(),pthread_self());
}

void *thr_fn(void *arg)
{
printids (“new thread: “);
}

int main()
{
int err;
pthread_t tid;
err=pthread_create(&tid,NULL,thr_fn,NULL);
if(err=0)
    printf(“can’t create thread:%s/n”,strerror(err));
printids(“main thread: “);
sleep(1);
exit(0);
}
关于进程的编译我们都要加上参数 –lpthread 否则提示找不到函数的错误。

具体编译方法是
cc –lpthread –o gettid gettid.c
运行结果为

main thread: pid 14954 tid 134529024
new thread: pid 14954 tid 134530048

进程的所有信息对该进程的所有线程是共享的----验证

线程的终止
线程是依进程而存在的,当进程终止时,线程也就终止了。当然也有在不终止整个进程的情况下停止它的控制流。

1)线程只是从启动例程中返回,返回值是线程的退出码。

2)线程可以被同一进程中的其他线程取消。

3)线程调用
pthread_exit.
函数4

名称:
pthread_exit
功能: 终止一个线程

头文件:
#include <pthread.h>
函数原形:
void pthread_exit(void *rval_ptr);
参数:

返回值:

rval_prt
是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以调用pthread_join函数访问到这个指针。

函数5

名称:
pthread_join
功能: 获得线程的终止状态

头文件:
#include <pthread.h>
函数原形:
int pthread_join(pthread_t thread,void **rval_ptr);
参数:

返回值: 若成功返回0,否则返回错误编号。


当一个线程通过调用pthread_exit退出或者简单地从启动历程中返回时,进程中的其他线程可以通过调用pthread_join函数获得进程的退出状态。调用pthread_join进程将一直阻塞,直到指定的线程调用pthread_exit,从启动例程中或者被取消。(直到指定的线程返回)

如果线程只是从它的启动历程返回,rval_ptr将包含返回码。

#include <pthread.h>
#include <string.h>

#include <stdio.h>
void *thr_fn1(void *arg)
{
printf(“thread 1 returning/n”);
return((void *)1);
}
void *thr_fn2(void *arg)
{
printf(“thread 2 exiting/n”);
return((void *)2);
}
int main()
{
pthread_t tid1,tid2;
void *tret;
pthread_create(&tid1,NULL,thr_fn1,NULL);
pthread_create(&tid2,NULL,thr_fn2,NULL);
pthread_join(tid1,&tret); //blocked until tid1 return
printf(“thread 1 exit code %d/n”,(int)tret);
pthread_join(tid2,&tret); //blocked until tid2 return
printf(“thread 2 exit code %d/n”,(int)tret);
exit(0);
}

运行结果是:

thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2


函数6
名称: pthread_detach
功能: 使线程进入分离状态。

头文件:
#include <pthread.h>
函数原形:
int pthread_detach(pthread_t tid);
参数:

返回值: 若成功则返回0,否则返回错误编号。


在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL.pthread_detach调用可以用于使线程进入分离状态。

线程的分离状态决定一个线程以什么样的方式来终止自己,分离状态或者非分离状态。线程的默认属性,一般是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源.程序员应该根据自己的需要,选择适当的分离状态。

函数7
名称:
pthread_cancel
功能: 取消同一进程中的其他线程

头文件:

#include <pthread.h>
函数原形:
int pthread_cancel(pthread_t tid);
参数: tid  线程
id
返回值: 若成功返回0,否则返回错误编号。


在默认的情况下,pthread_cancel函数会使由tid标识的线程的行为表现为如同调用了参数为PTHEAD_CANCELEDpthread_exit函数,但是,线程可以选择忽略取消方式和控制取消方式。pthread_cancel并不等待线程终止,它仅仅提出请求。


函数8
名称:
pthread_cleanup_push/ pthread_cleanup_pop
功能: 线程清理处理程序

头文件:
#include <pthread.h>
函数原形: void pthread_cleanup_push(void (*rtn)(void *)
void *arg);
                      void pthread_cleanup _pop(int execute);
参数: rtn 处理程序入口地址 arg 传递给处理函数的参数

返回值:

execute
:为0,不执行清理函数;非0,执行清理函数。

pthread_cleanup_pop removes the most recently installed cleanup handler. 
If the execute argument is not 0, it also executes the handler,by calling the 
routine function with arguments arg.   If the execute argument is 0, 
the handler is only removed but not executed.


线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序,线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。
要注意的是如果线程是通过从他的启动例程中返回而终止的,它的处理程序就不会调用。还要注意清理处理程序是按照与它们安装时相反的顺序调用的。

 

清理函数只在下述三种情况下执行:
1,
调用pthread_exit函数
2,
响应取消请求时
3,
调用参数非0pthread_cleanup_pop函数

#include <pthread.h>
#include <stdio.h>

void cleanup(void *arg)
{
    printf(“cleanup: %s/n”,(char *)arg);
}

void *thr_fn(void *arg) /*
线程入口地址*/
{
printf(“thread start/n”);
pthread_cleanup_push(cleanup,”thread first handler”);/*
设置第一个线程处理程序
*/
pthread_cleanup_push(cleanup,”thread second handler”); /*
设置第二个线程处理程序
*/
printf(“thread push complete/n”);
pthread_cleanup_pop(0); /*
取消第一个线程处理程序
*/
pthread_cleanup_pop(0); /*
取消第二个线程处理程序
*/
}

int main()
{
pthread_t tid;
void *tret;

pthread_create(&tid,NULL,thr_fn,(void *)1); /*
创建一个线程
*/
pthread_join(tid,&tret); /*
获得线程终止状态
*/
printf(“thread exit code %d/n”,(int)tret);
}

八、一次性初始化

      
有时候我们需要对一些posix变量只进行一次初始化,如线程键(我下面会讲到)。如果我们进行多次初始化程序就会出现错误。

      
在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态初始化为0,而任何依赖于初始化的代码都能测试该变量。如果变量值仍然为0,则它能实行初始化,然后将变量置为1。以后检查的代码将跳过初始化。

      
但是在多线程程序设计中,事情就变的复杂的多。如果多个线程并发地执行初始化序列代码,2个线程可能发现控制变量为0,并且都实行初始化,而该过程本该仅仅执行一次。初始化的状态必须由互斥量保护。

      
如果我们需要对一个posix变量静态的初始化,可使用的方法是用一个互斥量对该变量的初始化进行控制。但有时候我们需要对该变量进行动态初始化,pthread_once就会方便的多。

互斥量---变量静态初始化

pthread_once---变量动态初始化
函数9.
名称:
pthread_once
功能: 一次性初始化

头文件:
#include <pthread.h>
函数原形:
  pthread_once_t once_control=PTHREAD_ONCE_INIT;
                      int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));
参数: once_control         控制变量  init_routine   初始化函数

返回值: 若成功返回0,若失败返回错误编号。

类型为pthread_once_t的变量是一个控制变量。控制变量必须使用PTHREAD_ONCE_INIT宏静态地初始化。

       pthread_once
函数首先检查控制变量,判断是否已经完成初始化,如果完成就简单地返回;否则,pthread_once调用初始化函数,并且记录下初始化被完成。如果在一个线程初始时,另外的线程调用pthread_once,则调用线程等待,直到那个现成完成初始化返回。


下面就是该函数的程序例子:对互斥量的一次性初始化

#include <pthread.h>
#include <stdio.h>
pthread_once_t once=PTHREAD_ONCE_INIT;
pthread_mutex_t mutex;
 /*互斥量,我们后面会讲到
*/

void once_init_routine(void)
 /*一次初始化函数
*/
{
    int status;
status=pthread_mutex_init(&mutex,NULL);/*
初始化互斥量
*/
if(status==0)
    printf(“Init success!,My id is %u/n”,pthread_self());
}

void *child_thread(void *arg)
{
printf(“I’m child ,My id is %u/n”,pthread_self());
    pthread_once(&once,once_init_routine); /*
子线程调用一次性初始化函数
*/
}

int main(int argc,char *argv[ ])
{
pthread_t child_thread_id;

pthread_create(&child_thread_id,NULL,child_thread,NULL);/*
创建子线程
*/
printf(“I’m father,my id is %u/n”,pthread_self());
pthread_once(&once,once_init_routine);/*
父线程调用一次性初始化函数
*/
pthread_join(child_thread_id,NULL);
}
程序运行结果如下:

./once
I’m father,My id is 3086874304
Init success!,My id is 3086874304
I’m child, My id is 3086871472    
从上面的结果可以看到当主函数初始化成功后,子函数初始化失败。

 

修改的例子,对全局变量的一次性初始化:

#include <pthread.h>

#include <stdio.h>

pthread_once_t once=PTHREAD_ONCE_INIT;

pthread_mutex_t mutex;

int flag;

 

void once_init_routine2(void)

{

    int status;

    flag=2;

    printf("flag=%d/n",flag);

}

 

void once_init_routine1(void)

{

    int status;

    flag=1;

    printf("flag=%d/n",flag);

}

 

void *child_thread(void *arg)

{

printf("I'm child ,My id is %u/n",pthread_self());

    pthread_once(&once,once_init_routine1); /*锟斤拷痰锟揭伙拷猿始锟?/

}

 

int main(int argc,char *argv[ ])

{

pthread_t child_thread_id;

 

pthread_create(&child_thread_id,NULL,child_thread,NULL);

printf("I'm father,my id is %u/n",pthread_self());

pthread_once(&once,once_init_routine2);

pthread_join(child_thread_id,NULL);

}程序运行结果如下:
./once
I’m child,My id is 3076438960
flag=1
I’m father, My id is 3076442336  


九、线程的私有数据
在进程内的所有线程共享相同的地址空间,任何声明为静态或外部的变量,或在进程堆声明的变量,都可以被进程所有的线程读写。那怎样才能使线程拥有自己的私有数据呢。

 posix
提供了一种方法,创建线程键。线程键再与不同的线程私有数据地址进行关联。

线程键---线程的私有数据

函数10.
名称:
pthread_key_create
功能: 建立线程私有数据键

头文件:

#include <pthread.h>
函数原形:
int pthread_key_create(pthread_key *key,void(*destructor)(void *));
参数: key         私有数据键 destructor    清理函数

返回值: 若成功返回0,若失败返回错误编号。


第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数(清理函数),如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once一起使用,为了让这个键只被创建一次。函数pthread_once声明一个初始化函数,第一次调用pthread_once时它执行这个函数,以后的调用将被它忽略。


下面是程序例子:

#include <pthread.h>
#include <stdio.h>
pthread_key_t tsd_key;
pthread_once_t key_once=PTHREAD_ONCE_INIT;

void once_routine(void)
{
int status;

status=pthread_key_create(&tsd_key,NULL);/*
初始化线程私有数据键
*/
if(status=0)
    printf(“Key create success! My id is %u/n”,pthread_self());
}

void *child_thread(void *arg)
{
printf(“I’m child,My id is %u/n”,pthread_self());
pthread_once(&key_once,once_routine);/*
调用一次性初始化函数
*/
}

int main(int argc,char *argv[ ])
{
pthread_t child_thread_id;
pthread_create(&child_thread_id,NULL,child_thread,NULL);
printf(“I’m father,my id is %u/n”,pthread_self());
pthread_once(&key_once,once_routine);
}
程序运行结果如下:

I’m father,My id is 3086231232
Key create success! My id is 3086231232
I’m child,My id is 2086228400

 

原创粉丝点击