UNIX/Linux-线程控制(实例入门篇)

UNIX线程控制

线程属性

在创建线程时,可以用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来。可以用pthread_attr_init函数初始化pthread_attr_t结构。

#include

int  pthread_attr_init (pthread_attr_t* attr) ;

int  pthread_attr_destroy (pthread_attr_t* attr) ;

线程属性包括:线程的分离状态属性、线程栈末尾的警戒缓冲区大小、线程栈的最低地址、线程栈的大小……

 

分离线程

如果对现有的某个线程的终止状态不感兴趣,可以使用pthread_detach函数让操作系统在线程退出时回收它所占用的资源。(分离:不与其他线程联系,即不把自己的终止状态返回给线程)

如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL

如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构的相关属性,可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为:PTHREAD_CREATE_DETACHED,以分离状态启动。或设置为PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序就可以获取线程的终止状态。

 

线程私有数据

有时候我们可能会有这种需求:需要一个在线程内的全局变量,即此变量只能让此线程使用,其他线程不能访问。(线程会调用多个模块,各模块之间可能需要一个全局变量)

(当然,我们也可以在线程函数中定义变量,把其地址传给各个子模块函数,这样做毕竟不方便,使函数的接口变的复杂)

 

在分配线程私有数据之前,需要创建与该数据关联的键。这个键将用于获取对线程私有数据的访问权。

#include

int  pthread_key_create(pthread_key_t * keyp, void (*destructor)(void *)) ;

创建的键存放在keyp指向的内存单元,除创建键以外,pthread_key_create可以选择为该键关联析构函数,当线程调用pthread_exit或线程执行返回,正常退出时,析构函数就会被调用。但如果线程调用了exit_exit_Exitabort或出现其他非正常的退出时,就不会调用析构函数。

线程通常使用malloc为线程私有数据分配内存空间,析构函数通常释放已分配的内存。

 

键一旦创建,就可以通过调用pthread_setspecific函数把键和线程私有数据关联起来。可以通过pthread_getspecific函数获得线程私有数据的地址。

#include

int  pthread_setspecific(pthread_key_t key,  const void * value) ;

void*  pthread_getspecific(pthread_key_t key) ;

如果没有线程私有数据值与键关联,pthread_getspecific将返回一个空指针,可以据此来确定是否需要调用pthread_setspecific

 

【示例】

//线程私有数据的例子
//
//
#include 
#include 
#include 
#include  

//线程私有数据的键 对所有线程是可见的(但只有创建它的线程是可以访问的)
static pthread_key_t key ; 

//以下两个条件变量用于线程间的同步
struct
{
    pthread_cond_t cond ;
    pthread_mutex_t mutex ;
    int nready ;        //允许线程继续执行
} condForThread1 = {
    PTHREAD_COND_INITIALIZER ,
    PTHREAD_MUTEX_INITIALIZER
} ;

struct
{
    pthread_cond_t cond ;
    pthread_mutex_t mutex ;
    int nready ;        //允许线程继续执行
} condForThread2 = {
    PTHREAD_COND_INITIALIZER ,
    PTHREAD_MUTEX_INITIALIZER
} ;

void* thread1(void* arg);
void* thread2(void* arg);
void childfunc();

int 
main(void)
{
    pthread_t tid1, tid2 ;
    
    pthread_create(&tid1, NULL, thread1, NULL) ;
    pthread_create(&tid2, NULL, thread2, NULL) ;

    pthread_join(tid1, NULL) ;
    pthread_join(tid2, NULL) ;

    return 0 ;
}

void*
thread1(void* arg)
{
    char* privateBuf = NULL ;
    int   bufsize = 10 ;
    int   i = 0 ;

    pthread_key_create(&key, free) ; //创建键

    //获取键的关联数据的指针(检查此键是否已经关联了数据)
    privateBuf = (char*)pthread_getspecific(key) ; 
    if (privateBuf == NULL)
    {
        privateBuf = malloc(bufsize) ;
        if (privateBuf == NULL)
            return ;
    }
    //设置 键与数据的关联
    pthread_setspecific(key, privateBuf) ;

    childfunc() ; //子模块访问线程的私有数据

    //打印buf
    for (i = 0; i < bufsize; ++i)
    {
        printf("%d,\n", privateBuf[i]) ;
    }

    //向线程2发信号 唤醒线程2
    condForThread2.nready = 1 ;
    pthread_cond_signal(&condForThread2.cond) ;

    //挂起 等待来自线程1的信号
    pthread_mutex_lock(&condForThread1.mutex) ;
    pthread_cond_wait(&condForThread1.cond, &condForThread1.mutex) ;
    pthread_mutex_unlock(&condForThread1.mutex) ;

}

void 
childfunc()
{
    int i = 0 ;
    char* buf ;

    buf = (char*)pthread_getspecific(key) ; 
    for (i = 0; i < 10; ++i)
    {
        buf[i] = i ;
    }
}

void*
thread2(void* arg)
{
    char* privateBuf = NULL ;
    int   bufsize = 10 ;
    int   i = 0 ;

    //等待来自线程1的信号
    pthread_mutex_lock(&condForThread2.mutex) ;
    pthread_cond_wait(&condForThread2.cond, &condForThread2.mutex) ;
    pthread_mutex_unlock(&condForThread2.mutex) ;

    //获取键的关联数据的指针(会引发段错误,不允许其他线程访问key)
    privateBuf = (char*)pthread_getspecific(key) ; 

    //打印buf
    for (i = 0; i < bufsize; ++i)
    {
        printf("*%d,\n", privateBuf[i]) ;
    }

    //向线程1发信号
    condForThread1.nready = 1 ;
    pthread_cond_signal(&condForThread1.cond) ;
}

 

线程和信号

进程中的信号是递送到单个线程的,如果信号与硬件故障或计时器超时相关,该信号就被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程

 

每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的

 

在多线程环境下设置信号屏蔽字:使用pthread_sigmask函数(因为sigprocmask的行为在多线程的进程中没有定义)

#include

int  pthread_sigmask (int how,  const sigset_t * set,  sigset_t* oset) ;

此函数与sigprocmask函数基本相同。

 

线程等待信号

#include

int  sigwait (const sigset_t * set,  int * signop) ;

参数set指出了线程等待的信号集,参数signop为返回值是等到的是哪个信号

注意:

为了避免错误动作发生,线程在调用sigwait之前,必须阻塞那些它正在等待的信号。sigwait函数会自动取消信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait将恢复线程的信号屏蔽字。

 

使用sigwait的好处在于它可以简化信号处理。

为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专用线程做信号处理。因为sigwait会解除信号的阻塞状态,所以只有一个线程可以用于信号的接收。这使得对主线程进行编码时不必担心来自这些信号的中断。

【注意】闹钟定时器是进程资源,并且所有的线程共享相同的alarm。所以进程中的多个线程不可能互不干扰地使用闹钟定时器。

 

【示例】

//线程对信号的处理
//SIGQUIT 对应键盘 ctrl+\
//SIGINT  对应键盘 ctrl+c
#include 
#include 
#include 
#include 
#include  

sigset_t mask ; //信号集

int  quitflag ; //收到退出信号的标记
pthread_cond_t waitcond = PTHREAD_COND_INITIALIZER ;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER ;

void* thr_signal_handle(void* arg) ;

int 
main(void)
{
    int       err ;
    sigset_t  oldmask ;
    pthread_t tid ;
    
    sigemptyset(&mask) ;
    sigaddset(&mask, SIGINT) ;
    sigaddset(&mask, SIGQUIT) ;

    //设置进程信号屏蔽字
    if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        perror("SIG_BLOCK error!\n") ;

    //创建线程
    if ((err = pthread_create(&tid, NULL, thr_signal_handle, NULL)) != 0)
        perror("can't create thread!\n") ;

    //等待线程返回消息
    pthread_mutex_lock(&lock) ;
    while (quitflag == 0)
        pthread_cond_wait(&waitcond, &lock) ;
    pthread_mutex_unlock(&lock) ;
    quitflag = 0 ;

    //恢复原进程信号屏蔽字
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        perror("SIG_SETMASK error!\n") ;

    exit(0) ;
}

//专门处理信号的线程
void*
thr_signal_handle(void* arg)
{
    int err, signo ;

    for(;;)
    {
        if ((err = sigwait(&mask, &signo)) != 0)
            perror("sigwait error!\n") ;
        switch(signo)
        {
        case SIGINT:
            printf("\ninterrupt\n") ;
        break ;
        case SIGQUIT:
            //向主线程发送通知
            pthread_mutex_lock(&lock) ;
            quitflag = 1 ;
            pthread_mutex_unlock(&lock) ;
            pthread_cond_signal(&waitcond) ;
            return ;
        default:
            printf("unexpected signal %d\n", signo) ;
            exit(1) ;
        }
    }//for(;;)
}

 

向线程发送信号

要把信号发送到进程,可以调用kill。要把信号发送到线程,可以调用pthread_kill。

#include

int  pthread_kill (pthread_t tid, int signo) ;

 

【示例】

//线程间信号的发送
#include 
#include 
#include 
#include 
#include  

sigset_t mask ; //信号集
pthread_t tidbuf[5] ; //存储进程中所有线程的ID

int  flag ; 
pthread_cond_t waitcond = PTHREAD_COND_INITIALIZER ;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER ;

void* thr1(void* arg) ;
void* thr2(void* arg) ;

int 
main(void)
{
    int       err ;
    sigset_t  oldmask ;
    pthread_t tid ;
    
    sigemptyset(&mask) ;
    sigaddset(&mask, SIGUSR1) ;

    //阻塞要等待的信号
    if ((err = pthread_sigmask(SIG_BLOCK, &mask, &oldmask)) != 0)
        perror("SIG_BLOCK error!\n") ;

    //创建线程
    if ((err = pthread_create(&tidbuf[0], NULL, thr1, NULL)) != 0)
        perror("can't create thread!\n") ;
    if ((err = pthread_create(&tidbuf[1], NULL, thr2, NULL)) != 0)
        perror("can't create thread!\n") ;


    //等待线程结束
    pthread_join(tidbuf[0], NULL) ;
    pthread_join(tidbuf[1], NULL) ;

    exit(0) ;
}

void*
thr1(void* arg)
{
    int err, signo ;

    //等待线程2起来
    pthread_mutex_lock(&lock) ;
    while(flag == 0)
        pthread_cond_wait(&waitcond, &lock) ;
    pthread_mutex_unlock(&lock) ;

    //等待接收来自线程2的信号
    if ((err = sigwait(&mask, &signo)) != 0)
        perror("sigwait error!\n") ;
    if (signo == SIGUSR1)
        printf("\nI catched the signal from thread2!\n") ;
}

//
void*
thr2(void* arg)
{
    int err, signo ;

    //起来之后向线程1发送信号
    pthread_mutex_lock(&lock) ;
    flag = 1 ;
    pthread_mutex_unlock(&lock) ;
    pthread_cond_signal(&waitcond) ;

    pthread_kill(tidbuf[0], SIGUSR1) ; 
}


【注意】很多linux系统在编译时,没有自动链接线程库,需要在编译时指定链接:例 gcc a.c -l pthread 

 

你可能感兴趣的:(UNIX/Linux,UNIX/Linux,IPC,接口详解)