linux 线程

来源:互联网 发布:淘宝视频取消自动播放 编辑:程序博客网 时间:2024/05/29 15:04

POSIX线程(P线程)编程

 

要想知道正在使用的是哪个P线程库:

$ getconf GNU_LIBPTHREAD_VERSION

执行这个命令会显示出LinuxThreads或NPTL及其版本号

        

进程和线程都有控制流,两者都能同时运行,线程共享数据,进程不共享。

创建线程的时候,线程唯一独有的元素是线程独有的栈,线程的代码和全局变量都是共同的;但进程会复制代码、数据空间、内存栈。

         一个GNU/Linux进程可以创建和管理多个线程。线程由线程描述符确定,系统中每个线程的描述符都是唯一的,每个线程都有一个私有的栈和一个独有的上下文环境(程序计数器和存储寄存器等)。线程共享数据空间,他们共享的东西就不只是用户的数据了。例如,打开文件的描述符和套接字是共享的。因此当一个多线程应用程序使用套接字或文件时,要防止多重访问同一资源的情况。

         编写多线程应用程序时要注意线程的数据共享问题。

 

线程函数基础

 

之前API函数都有一个共同的模式:遇到错误时,返回 -1,错误码保存在进程变量errno中。线程API函数成功时返回0,在遇到错误时,返回一个大于0的错误码。

 

P线程API

所有的多线程应用程序都要使pthread函数原型和相应的符号可用。故要包含<pthread.h>

所有的多线程应用程序都必须创建线程,并在最后销毁线程。

 Int  pthread_create(pthread_t   *thread, pthread_attr_t* attr,

                                               Void*(*start_routine)(void*),  void*arg);

 

Int pthread_exit(void* retval);

创建一个新的线程,调用pthread_create,并把pthread_t对象和一个函数(start_routine)结合起来。这个函数表示线程执行的最高层代码。可以由pthread_attr_t(通过pthread_attr_init)提供一系列选项进行属性设置。第四个参数(arg)是在线程创建是时传入的可选参数。

#include<pthread.h>

#include<stdlib.h>

#include<stdio.h>

#include<string.h>

#include<errno.h>

Void * ThreadFunc(void* arg)  //线程体函数,运行结束时,自身调用pthread_exit结束

{

         printf(“Thread  ran\n”);

         pthread_exit(arg);

}

Int main()

{

         Int  ret;

         pthread_t  mythread;

         ret= pthread_create(& mythread, NULL, ThreadFunc, MULL);

//ThreadFunc为线程执行函数

         If(ret  != 0)

         {

                   Printf(“Can’tcreate  pthread(%s)\n”,  strerror(errno));

                   exit(-1);

}

return 0;

}

 

 

线程管理

 

函数pthread_self可以用来取得自己独特的描述符;  pthread_t  pthread_self();

大多数应用程序需要一些初始化,对于多线程应用程序,初始化很困难,函数pthread_once允许开发人员为一个多线程应用程序创建一个初始化子进程。只会执行一次。

第一个调用pthread_once的线程调用initialize_app,之后调用pthread_once的线程不会再调用initialize_app.

//用pthread_once 提供一个单次使用的初始化函数

#include<pthread.h>

Pthread_once_t  my_init_mutex =  pthread_once_init;

Void  initialize_app(void)

{

         //只执行一次的初始化函数

}

Void* myThread(void* arg)

{

         Pthread_once(&my_init_mutex,  initialize_app);

}

 

线程同步

 

让线程创建者等待线程结束(也成为“加入”线程),该功能由API函数pthread_join提供。在调用pthread_join时,会挂起调用线程直到加入的线程完成。在加入线程完成后,调用线程会从pthread_join的返回值获得到加入线程的终止状态。

Int phtread_join( pthread_t  th,  void ** thread_return);

参数th是想要加入的线程,该参数由pthread_create返回或由线程调用pthread_self获取。参数thread_return可以是NULL,表明不捕获线程的返回状态。

//注意: 用默认属性经pthread_create创建的线程都是可以加入的,如果线程的属性设置为detached,则该线程是不可加入的(因为它被设置为与创建线程分离)。

 

         很多情况下,一个线程被建立之后,就不再需要在意它了。那种情况下,可以把它设置为分离的线程,创建者和线程自己都可以完成分离操作,还可以在创建线程时就将其设置为分离的线程(作为属性设置的一部分)。在一个线程被分离之后,它就不能再被加入了。

Int pthread_detach(pthread_t  th);

//在线程中调用pthread_detach 把线程分离出去

Void * myThread(void * arg)

{

         printf(“Thread  %d started\n”, (int)arg);

         pthread_detach(pthread_self());

         Pthread_exit(arg);

}

///上程序中,线程执行函数中,自己将自己和调用线程分开,该线程退出时,所占用的资源会立即释放(因为这个线程已是分离线程,不会再加入其它线程)。函数pthread_detach成功时返回零,发生错误时返回非零值。

 

线程互斥

 

互斥是一个保证线程在关键区正常执行的变量,这些关键区只能由线程独占访问,如果不加保护的话,会导致数据被毁。

         要创建一个互斥,只需要声明一个表示互斥的变量,然后用特殊符号常量初始化。

pthread_mutex_t  myMutex =  PTHREAD_MUTEX_INITIALIZER;

互斥初始化可以有不同的特殊符号常量:

PTHREAD_MUTEX_INITIALIZER                   快速互斥

PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP                   递归互斥

PTHREAD_ERROCHECK_MUTEX_INITIALIZER_NP     检查错误的互斥

 

有了一个互斥,可以锁定或解锁它,从而创建关键区。通过pthread_mutex_lock和pthread_mutex_unlock来实现锁定和解锁。函数pthread_mutex_trylock用于尝试锁定互斥,但如果互斥已经锁定,它不会阻断。可以用pthread_mutex_destroy来销毁已经存在的互斥。

Int thread_mutex_lock(  pthread_mutex_t  *mutex);

Int thread_mutex_unlock ( pthread_mutex_t  *mutex);

Int thread_mutex_trylock( pthread_mutex_t  *mutex);

Int thread_mutex_destroy( pthread_mutex_t  *mutex);

 

锁定一个线程意味着进入了一个关键区,在互斥锁定后,可以安全的进入关键区而不必担心数据毁损或多重访问,要退出关键区,需要解锁互斥,然后退出。

Pthread_mutex_t  cntr_mutex = PTHREAD_MUTEX_INITIALIZER;

……

assert(pthread_mutex_lock(&cntr_mutex)== 0);

//关键区

//增加保护计数

Counter ++;

assert(pthread_mutex_unlock(&cntr_mutex)  == 0);

 

pthread_mutex_trylock 操作的意义在于如不能锁定互斥,或许可以做点别的事情而不是阻断在pthread_mutex_lock调用上:

ret =pthread_mutex_trylock(&cntr_mutex);

if (ret == EBUSY)

{

         //不能锁定,做其他的事情

}

Else if (ret == EINVAL)

{

         //关键区错误

         Assert(0);

}

Else

{

         //关键区操作

ret =thread_mutex_unlock(&cntr_mutex);

}

 

要销毁线程,调用pthread_mutex_destroy函数,函数pthread_mutex_destroy仅在互斥当前没有被任何线程锁定时才能成功执行,如果互斥正被锁定,函数会失败并返回错误码:EBUSY,

Ret =pthread_mutex_destroy(&cntr_mutex);

If (ret == EBUSY)

{

         //互斥被锁定,无法销毁

}

Else

{

         //互斥被销毁

}

 

线程条件变量

 

条件变量是一个特殊的线程结构体,允许一个线程基于条件唤醒另一个线程。条件变量允许一个线程等待某个事件,让另一个线程在事件发生时向它发出信号。

P线程API提供了很多支持条件变量的函数,这些函数提供条件变量的创建、等待、信号和销毁功能。

Int pthread_cond_wait(pthread_cond_t *cond,  pthread_mutex_t  *mutex);

Int pthread_cond_timedwait(pthread_cond_t * cond,  pthread_mutex_t * mutex,

                                                                 Conststruct timespec *abstime);

Int pthread_cond_signal(pthread_cond_t *cond);

Int pthread_cond_broadcast(pthread_cond_t *cond);

Int pthread_cond_destroy(pthread_cond_t *cond);

 

要创建一个条件变量,只需要创建一个pthread_cond_t类型的变量,把它设置为PTHREAD_COND_INITIALIZER就可以完成初始化(和互斥的创建于初始化相似)。

Pthread_cond_t  recoveryCond = PTHREAD_COND_INITIALIZER;

条件变量要求互斥的存在,并会和互斥联合工作。

(创建互斥:pthread_mutex_t  recoveryMutex= PTHREAD_MUTEX_INITIALIZER)

 

向一个线程发送信号:

1、  互斥先锁定

2、  调用signal函数

3、  完成后 再解锁互斥

向一个线程发送信号需要调用 pthread_cond_signal函数:

pthread_mutex_lock(&recoveryMutex);

pthread_cond_signal(&recoveryCond);

pthread_mutex_unlock(&recoveryMutex);

解锁互斥后,指定的一个线程收到信号并继续执行。在互斥解锁后,一系列线程都会恢复运行(不过他们也依赖于互斥,所以实际上是一个接一个的恢复运行)

 

P 线程API支持把时间等待作为条件等待的一种,函数pthread_cond_timewait,允许调用者设定一个绝对时间,用于规定什么时候放弃任务返回调用者

Struct timeval  currentTime;

Struct timespec  expireTime;

Int ret;

….

Assert(pthread_mutex_lock(&recoveryMutex)== 0)

gettimeofday(&currentTime);

expireTime.tv_sec = currentTime.tv_sec + 1;

expireTime.tv_nsec =currentTime.tv_usec*1000;

ret = 0;

while((workload  < MAX_NORMAL_WORKLOAD)&&(ret !=ETIMEOUT))

{

         ret= pthread_cond_timewait(&recoveryCond, &recoveryMutex,&expireTime);

}

If (ret == ETIMEOUT)

{

         //

}

Else

{

         //条件到达时,执行

}

assert(pthread_mutex_unlock(&recoveryMutex)== 0);

 

原创粉丝点击