现在的位置: 首页 > 综合 > 正文

APUE学习笔记——第十一章 线程

2013年10月27日 ⁄ 综合 ⁄ 共 9158字 ⁄ 字号 评论关闭

典型的UNIX进程可以看成只有一个控制线程:一个进程在同一时刻只能做一件事情。有了多个控制线程之后,在程序设计时可以把进程设计成在同一时刻能够做不止一件事,每个线程处理各自独立的任务。使用线程有很多好处:
(1)、通过为每种事件类型的处理分配单独的线程,能够简化处理异步事件的代码。
(2)、多个线程自动地可以访问相同的存储地址空间和文件描述符
(3)、将一个问题分解为多个程序,改善整个程序的吞吐量

(4)、使用多线程改善交互程序的响应时间。

1、线程标识
就像每个进程有一个进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中是唯一的,但是线程ID不同,线程ID只在它所属的进程环境中有效。进程ID使用pid_t数据类型来表示,是一个非负整数。线程ID则是用pthread_t数据类型来表示,实现的时候可以用一个结果来代表pthread_t数据类型,所以可移植的操作系统实现不能把它当作整数处理。必须使用函数来对两个线程ID进程比较:
#include <pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2)     返回值:若相等则返回非0值,否则返回0
线程可以通过调用pthread_self函数获取自身的线程ID
#include <pthread.h>
pthread_t pthread_self(void);

2、线程的创建

    新增的线程可以通过调用pthread_create函数创建:
#include <pthread.h>
int pthread_create(pthread_t *tid,const pthread_attr_t *attr,void* (*start_rtn)(void),void* arg)
返回值:成功返回0,否则返回错误编号
当pthread_create成功返回时,由tid指向的内存单元被设置为新创建线程的线程ID,attr参数用于指定各种不同的线程属性,可以暂时设置为NULL,创建默认属性的线程,新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,则需要把这些参数放到一个结构体中,然后把结构体的地址作为arg参数传入。

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

void printid(const char *str){
    printf("%s, pid: %d, tid: %u\n",str,getpid(),pthread_self());
}

void * thread_func(void *arg){
    printid("new thread: ");
    return (void*)0;
}

int main(){
    pthread_t ntid;
    pthread_create(&ntid,NULL,thread_func,NULL);
    printid("main thread: ");
    sleep(2);
    exit(0);
}

由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数。

例如:gcc thread.c -lpthread -o thread

3、线程终止
如果进程中的任一线程调用exit,_Exit或_exit,那么整个进程就会终止。单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制了。
(1)、线程只是从启动例程中返回,返回值是线程的退出码
(2)、线程可以被统一进程中的其他线程取消
(3)、线程调用pthread_exit函数
#include <pthread.h>
void pthread_exit(void* rval_ptr)
rval_ptr是一个无类型指针,与传递启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
#include <pthread.h>
int pthread_join(pthread_t tid, void* *rval_ptr)返回值:成功返回0,否则返回错误编号。
需要注意的是pthread_create和pthread_exit函数的无类型指针参数能够传递的数值可以不止一个,该指针可以传递包含复杂信息的结构地址,这个结构必须所使用的内存必须在调用者用完以后必须仍然有效,否则会出现无法访问或非法。例如在线程的栈上分配了该结构,例如下面程序,参数不正确使用,导致结果错误,
例如:

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

struct foo{int a,b,c,d;};

void print(const char *str,const struct foo *fp){
    puts(str);
    printf("addr: 0x%x\n",(unsigned int)fp);
    printf("a = %d\n",fp->a);
    printf("b = %d\n",fp->b);
    printf("c = %d\n",fp->c);
    printf("d = %d\n",fp->d);
}

void * thread_func1(void *arg){
    struct foo foo = {1,2,3,4};//线程栈上分配内存
    print("Thread 1:",&foo);//使用完之后就失效了
    pthread_exit((void*)&foo);
}

void * thread_func2(void *arg){
    printf("Thread 2: ID is %u\n",pthread_self());
    pthread_exit((void*)3);
}

int main(){
    pthread_t tid1,tid2;
    struct foo *fp;
    //fp  =(struct foo*)malloc(sizeof(struct foo));
    pthread_create(&tid1,NULL,thread_func1,NULL);
    pthread_join(tid1,(void*)&fp);
    sleep(1);
    pthread_create(&tid2,NULL,thread_func2,NULL);
    sleep(1);
    print("parent: ",fp);
    exit(0);
}

运行结果:

Thread 1:
addr: 0xb7817370
a = 1
b = 2
c = 3
d = 4
Thread 2: ID is 3078716272
parent: 
addr: 0xb7817370
a = 12097182
b = -1216253052
c = 12431348
d = 12423792
可以发现,两个的地址一样都是0xb7817370,解决的办法是使用全局变量或者使用malloc函数分配内存。

正确的方法应该将thread_func1中换成如下形式:

struct foo *foo = (struct foo*)malloc(sizeof(struct foo));//申请内存
foo->a = 1, foo->b = 2,foo->c = 3,foo->d = 4;
pthread_exit((void*)foo)//这里和上面的不同,C语言的指针真心麻烦

线程调用pthread_cancel函数来请求取消同一进程中的其他线程,并不等待线程终止,只是提出请求而已。
#include <pthread.h>
int pthread_cancel(pthread_t tid)。函数功能等价于使得tid标识的线程调用pthread_exit(PTHREAD_CANCELED)。
线程清理处理程序,类似进程退出时候清理函数,线程可以建立多个清理处理程序,处理程序记录在栈中,执行顺序与注册顺序相反。函数原型如下:
void pthread_cleanup_push(void (*routine)(void *),void *arg);   //注册清理函数
void pthread_cleanup_pop(int execute);  //删除清理程序,若execute=0,清理函数将不被调用
两个函数限制:必须在线程相同的作用域中以匹配队的形式使用
清理函数在以下三种情况会调用:(1)调用pthread_exit;(2)响应取消请求;(3)用非零execute参数调用pthread_cleanup_pop。pthread_cleanup_pop函数删除上次pthread_cleanup_push调用建立的清理处理程序。
具体例子详见课本。

在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL。pthread_detach调用可以用于使线程进入分离状态。
具体线程处于分离状态有啥好处暂时不知道。
#include <pthread.h>
int pthread_detach(pthread_t tid) 返回值:成功返回0,否则返回错误编号

4、线程同步

当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。只有多个线程存在同时读写同一变量时,需要对线程进行同步 。线程同步的方法:线程锁(互斥量)、读写锁、条件变量、信号量。

4.1 互斥量
可以通过使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。在这种方式下,每次只有一个线程可以向前执行。
互斥量用pthread_mutex_t数据类型来表示,在使用互斥量之前,必须首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存之前需要调用pthread_mutex_destroy
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
int pthread_mutex_destroy(pthread_mutex_t *mutex) 
返回值:成功返回0,否则返回错误编号
要用默认的属性初始化互斥量,只需把attr设置为NULL
对互斥量进行加锁,需要调用pthread_mutex_lock,解锁调用pthread_mutex_unlock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex)
int pthread_mutex_trylock(pthread_mutex_t* mutex)
int pthread_mutex_unlock(pthread_mutex_t* mutex)
如果不希望线程被阻塞,可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。

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

struct foo{
    int count;
    pthread_mutex_t lock;
};

struct foo* init(){
    struct foo *fp;
    if((fp = (struct foo*)malloc(sizeof(struct foo))) != NULL){
        fp->count = 1;
        if(pthread_mutex_init(&fp->lock,NULL) != 0) {
            free(fp);
            return NULL;
	}
    }
    return fp;
}

void add(struct foo *fp){
    pthread_mutex_lock(&fp->lock);
    fp->count++;
    printf("add: count is %d\n",fp->count);
    pthread_mutex_unlock(&fp->lock);
}

void sub(struct foo *fp){
    pthread_mutex_lock(&fp->lock);
    printf("sub: count is %d\n",fp->count - 1);
    if(--fp->count == 0){
        pthread_mutex_unlock(&fp->lock);
        pthread_mutex_destroy(&fp->lock);
        free(fp);
    }else	
        pthread_mutex_unlock(&fp->lock);
}

void * thread_func1(void *arg){
    struct foo *fp = (struct foo*)arg;
    add(fp);
    add(fp);
    pthread_exit((void*)1);
}

void * thread_func2(void *arg){ 
    struct foo *fp = (struct foo*)arg;
    sub(fp);
    pthread_exit((void*)2);
}

int main(){
    pthread_t tid1,tid2;
    void *ret;
    struct foo *fp = init();
    int err = pthread_create(&tid1,NULL,thread_func1,(void*)fp);
    pthread_create(&tid2,NULL,thread_func2,(void*)fp);
    pthread_join(tid1,&ret);
    printf("tid1 exit: %d\n",(int)ret);
    pthread_join(tid2,&ret);
    printf("tid2 exit: %d\n",(int)ret);
    exit(0);
}

4.2 读写锁

读写锁可以使读操作比互斥量有更高的并行性,互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。当读写锁是写加锁状态时,在解锁之前,所有试图对这个锁加锁的线程都会被阻塞;当读写锁在读加锁的状态时,所以试图以读模式对它进行加锁的线程都可以得到访问权,但如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有线程释放读锁。
一次只有一个线程可以占有写模式读写锁,而多个线程可以同时占有读模式的读写锁。当读操作较多,写操作较少时,可使用读写锁提高线程读并发性。读写锁数据类型为pthread_rwlock_t,操作函数如下:
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_mutex_t *mutex); 
int pthread_rwlock_trywrlock(pthread_mutex_t *mutex); 
返回值:成功返回0,否则返回错误编号
这些函数的意思都和互斥量函数意思差不多,这里不多解释,例子也不给出了。

4.3 条件变量

条件变量给多个线程提供了一个会合的机会,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护。线程在改变条件状态前必须先锁住互斥量,条件变量允许线程等待特定条件发生。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。条件变量类型为pthread_cond_t,使用前必须进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但如果条件变量是动态分配的,可以使用pthread_cond_init函数进行初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const structtimespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond);  //唤醒等待该条件的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒等待该条件的某个线程
返回值:成功返回0,否则返回错误编号

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

struct foo{
    int count;
    pthread_mutex_t lock;
    pthread_cond_t cond;
};

struct foo* init(){
    struct foo* fp;
    fp = (struct foo*)malloc(sizeof(struct foo));
    if(fp != NULL){
        fp->count = 0;
        pthread_mutex_init(&fp->lock,NULL);
        pthread_cond_init(&fp->cond,NULL);    
    }
    return fp;
}

void add(struct foo* fp){
    pthread_mutex_lock(&fp->lock);
    if(fp->count == 0)//只有当count变为0了才signal,不为0时根本不需要signal
        pthread_cond_signal(&fp->cond);
    fp->count ++;
    pthread_mutex_unlock(&fp->lock);
}

void sub(struct foo* fp){
    pthread_mutex_lock(&fp->lock);
    while(fp->count == 0) 
        pthread_cond_wait(&fp->cond,&fp->lock);
    fp->count--;
    pthread_mutex_unlock(&fp->lock);
}

void * thread_func1(void *arg){
    struct foo* fp = (struct foo*) arg;
    printf("Add before: %d\n",fp->count);
    add(fp);
    printf("Thread add count=%d\n",fp->count);
    pthread_exit((void*)1);
}

void * thread_func2(void*arg){
    struct foo* fp = (struct foo*)arg;
    printf("Sub before: %d\n",fp->count);
    sub(fp);
    printf("Thread sub count=%d\n",fp->count);
    pthread_exit((void*)2);
}

int main(){
    pthread_t tid1[5],tid2[5];
    struct foo* fp = init();
    int i;
    void* ret;
    for(i = 0 ; i < 5; i ++){
        pthread_create(&tid2[i],NULL,thread_func2,(void*)fp);
        sleep(1);
    }
    for(i = 0 ; i < 5 ; i ++){
        pthread_create(&tid1[i],NULL,thread_func1,(void*)fp);
        sleep(2);
    }
    for(i = 0 ; i < 5 ; i ++){
        pthread_join(tid1[i],&ret);
        printf("Thread add is: %d\n",(int)ret);
    }
    for(i = 0 ; i < 5 ; i ++){
        pthread_join(tid2[i],&ret);
        printf("Thread sub is: %d\n",(int)ret);
    }
    exit(0);
}

4.4 信号量

这里所指的信号量只是POSIX 的信号量 SYSTEM V的就不说了。这部分的知识在UNIX网络编程上,本书没有,给出两个比较好的Blog链接:

http://www.cnblogs.com/Anker/archive/2013/01/13/2858765.html

http://blog.csdn.net/wtz1985/article/details/3826291

抱歉!评论已关闭.