线程

来源:互联网 发布:蓝德资本网络非法传销 编辑:程序博客网 时间:2024/06/06 04:07

一个进程在同一时刻只能做一件事情,线程可以把程序设计成在同一时刻能够做多件事情,每个线程处理各自独立的任务。

使用线程的好处:
(1)为每种事件分配单独的线程、能够简化处理异步事件的代码;
(2)多个线程自动地可以访问相同的存储地址空间和文件描述符;
(3)将一个问题分解为多个程序,改善整个程序的吞吐量;
(4)使用多线程改善交互程序的响应时间。

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

进程与线程关系:进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。线程通常叫做轻型的进程。线程是在共享内存空间中并发执行的多道执行路径,他们共享一个进程的资源。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。

线程标识

进程ID在整个系统中是唯一的,但线程ID只在它所属的进程环境中有效。线程ID用pthread_t数据类型来表示,实现的时候用一个结构体来代表pthread_t数据类型。线程ID操作函数如下:

 #include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2);//比较两个线程ID,相等返回非零,否则返回0  pthread_t pthread_self(void);  //获取调用线程ID,返回调用线程的线程ID

Linux 3.2.0使用无符号整型标识pthread_t数据类型。

线程创建

#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_create成功返回时,新创建线程的线程ID会被设置成tidp指向的内存空间。
attr属性用于定制各种不同的线程属性。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <sys/types.h>#include <pthread.h>pthread_t   ntid;void printpthreadinfo(const char* str);void* thread_func(void *arg);int main(){    int err;    //创建一个新线程    err = pthread_create(&ntid,NULL,thread_func,NULL);    if(err != 0)    {        perror("pthread_create()");        exit(-1);    }    printpthreadinfo("main thread: ");    sleep(2);  //给新线程时间执行    return 0;}void printpthreadinfo(const char* str){    pid_t pid;    pthread_t tid;    pid = getpid();    tid = pthread_self();    printf("%s pid %u tid %u (0x%x)\n",str,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid);}void* thread_func(void *arg){    printpthreadinfo("new thread:  ");    return ((void*) 0);}

程序运行结果
这里写图片描述

线程终止

如果进程中任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止。
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
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 thread,void **rval_ptr);//成功返回0,否则返回错误编号

调用pthread_join后,调用线程将一直阻塞,直到调用pthread_exit、从启动例程中返回或者被取消。
如果线程简单地从它的启动例程返回,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就设置成PTHREAD_CANCELED。

在很多情况下,主线程生成并启动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到pthread_join()方法了。
即pthread_join()的作用可以这样理解:主线程等待子线程的终止。也就是主线程中pthread_join()方法后面的代码,只有等到子线程结束了才能执行。这说明pthread_join()也起到进程同步的作用。

写个程序创建两个线程,获取线程退出状态。程序如下:

#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <sys/types.h>void* thread_func1(void *arg);void* thread_func2(void *arg);int main(){    pthread_t tid1;    pthread_t tid2;    int       err;    void      *tret;    //创建新线程1    err = pthread_create(&tid1,NULL,thread_func1,NULL);    if(err != 0)    {        perror("pthread_create() error");        exit(-1);    }    //创建新线程2    err = pthread_create(&tid2,NULL,thread_func2,NULL);    if(err != 0)    {        perror("pthread_create() error");        exit(-1);    }    //等待线程1终止    err = pthread_join(tid1,&tret);    if(err != 0)    {        perror("pthread_join error");        exit(-1);    }    printf("thread1 exit code %d\n",(int)tret);    //等待线程2终止    err = pthread_join(tid2,&tret);    if(err != 0)    {        perror("pthread_join error");        exit(-1);    }    printf("thread2 exit code %d\n",(int)tret);    exit(0);}void* thread_func1(void *arg){    printf("thread1 is returning.\n");    return ((void*)1);}void* thread_func2(void *arg){    printf("thread2 exiting.\n");    pthread_exit((void*)2);}

这里写图片描述

需要注意的是pthread_create和pthread_exit函数的无类型指针参数能够传递的数值可以不止一个,该指针可以传递包含复杂信息的结构地址,这个结构所使用的内存必须在调用者用完以后仍然有效,否则会出现无法访问或非法。例如在线程的栈上分配了该结构,例如下面程序,参数不正确使用,导致结果错误,程序如下:

#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <sys/types.h>struct foo{    int a;    int b;    int c;    int d;};void printfoo(const char* str,const struct foo *fp);void* thread_func1(void *arg);void* thread_func2(void *arg);int main(){    pthread_t tid1;    pthread_t tid2;    int       err;    struct foo *pfoo;    err = pthread_create(&tid1,NULL,thread_func1,NULL);    if(err != 0)    {        perror("pthread_create() error");        exit(-1);    }    err = pthread_join(tid1,(void*)&pfoo);    if(err != 0)    {        perror("pthread_join error");        exit(-1);    }    sleep(1);    printf("Parent starting second thread.\n");    err = pthread_create(&tid2,NULL,thread_func2,NULL);    if(err != 0)    {        perror("pthread_create() error");        exit(-1);    }    sleep(1);    printfoo("parent: ",pfoo);    exit(0);}void printfoo(const char* str,const struct foo *fp){    puts(str);    printf(" structure at 0x%x\n",(unsigned int)fp);    printf("foo.a = %d\n",fp->a);    printf("foo.b = %d\n",fp->b);    printf("foo.c = %d\n",fp->c);    printf("foo.d = %d\n",fp->d);}void* thread_func1(void *arg){    struct foo f = {1,2,3,4};    printfoo("thread1:",&f);    pthread_exit((void*)&f);}void* thread_func2(void *arg){    printf("thread2: ID is %u\n",(unsigned int)pthread_self());    pthread_exit((void*)2);}

这里写图片描述

从结果可以看出,创建第一个线程时候,在该线程的栈中创建了一个结构,第二个线程栈覆盖了第一个线程栈,可以通过使用全局变量结构或者是使用malloc动态分配结构。

线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程。

#include <pthread.h>int pthread_cancel(pthread_t tid);//成功返回0,失败返回错误编号

pthread_cancel并不等待线程终止,它仅仅提出请求,线程可以选择忽略取消或者控制如何被取消。

线程可以安排它退出时需要调用的函数,这与进程在退出时可以用atexit函数安排退出是类似的。
如果线程是通过从它的启动例程中退出返回而终止的话,它的清理处理程序就不会被调用。

#include <pthread.h>void pthread_cleanup_push(void (*rtn)(void *),void *arg);void pthread_cleanup_pop(int execute);

如果execute参数设置为非0,则调用并删除上次pthread_cleanup_push调用建立的清理处理程序。
如果execute参数为0,则清理函数将不被调用(只删除)。

我们可以调用pthread_detach分离线程。

#include <pthread.h>int pthread_detach(pthread_t tid);

进程原语与线程原语的比较

进程原语 线程原语 描述 fork pthread_create 创建新的控制流 exit pthread_exit 从现有的控制流中退出 waitpid pthread_join 从控制流中得到退出状态 atexit pthread_cleanup_push 注册在退出控制流时调用的函数 getpid pthread_self 获取控制流的ID abort pthread_cancel 请求控制流的非正常退出

线程同步

一个线程可以修改的变量,其他线程在读取或者修改的时候,我们就需要对这些线程进行同步,确保他们在访问变量的存储内容时不会访问到无效的值。

#include <pthread.h>#include <stdio.h>void *print_msg1(void *arg){    int i=0;    for(i=0;i<5;i++)    {        printf("output : %d\n",i);        usleep(100);    }}void *print_msg2(void *arg){    int i=0;    for(i=5;i<10;i++)    {        printf("output : %d\n",i);        usleep(100);    } }int main(int argc,char** argv){    pthread_t id1;    pthread_t id2;    pthread_create(&id1,NULL,print_msg1,NULL);    pthread_create(&id2,NULL,print_msg2,NULL);    pthread_join(id1,NULL);    pthread_join(id2,NULL);    return 1;}

这里写图片描述
为了解决这个问题,线程不得不使用同步,同一时间只允许一个线程访问该变量。

互斥量

mutex是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。互斥量类型是为pthread_mutex_t的一个结构体。

在使用互斥量之前,必须对它进行初始化,可分为动态初始化和静态初始化。
以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

动态初始化则使用pthread_mutex_init函数:

#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);//成功返回0,错误返回出错码

要用默认的属性初始化互斥量,只需把attr设为NULL。

如果动态分配互斥量,在释放内存前需要调pthread_mutex_destroy。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提资源当前是没有被锁的状态。

互斥量有以下3种功能

#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_lock对互斥量进行加锁,如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。
可以使用pthread_mutex_unlock对互斥量解锁。
如果不希望被阻塞,可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果互斥量处于未锁住状态,则锁住互斥量,否则返回EBUSY。

#include <pthread.h>#include <stdio.h>pthread_mutex_t mutex ;void *print_msg1(void *arg){    int i=0;    pthread_mutex_lock(&mutex);    for(i=0;i<5;i++)    {        printf("output : %d\n",i);        usleep(100);    }    pthread_mutex_unlock(&mutex);}void *print_msg2(void *arg){    int i=0;    pthread_mutex_lock(&mutex);    for(i=5;i<10;i++)    {        printf("output : %d\n",i);        usleep(100);    }    pthread_mutex_unlock(&mutex);}int main(int argc,char** argv){    pthread_t id1;    pthread_t id2;    pthread_mutex_init(&mutex,NULL);    pthread_create(&id1,NULL,print_msg1,NULL);    pthread_create(&id2,NULL,print_msg2,NULL);    pthread_join(id1,NULL);    pthread_join(id2,NULL);    pthread_mutex_destroy(&mutex);    return 1;}

这里写图片描述

避免死锁

如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。
如果两个线程以相反的顺序锁住两个互斥量,也会导致死锁,两个线程都无法向前运行。
在同时需要两个互斥量时,让他们以相同的顺序加锁,这样可以避免死锁。

读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。
读写锁可以有3种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个所加锁的线程都会被阻塞。
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);

下面是读写锁的3种用法

#include <pthread.h>int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

与互斥量一样,读写锁定义了下面两个函数

#include <pthread.h>int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrwrock(pthread_rwlock_t *rwlock);

条件变量

  条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

初始化

  条件变量类型为pthread_cond_t,使用前必须进行初始化,也分为静态初始化和动态初始化,静态初始化是:

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

  动态初始化是调用pthread_cond_init函数,操作函数如下:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //attr为NULL表示初始化一个默认属性的条件变量 //返回值:函数成功返回0;任何其他返回值都表示错误

不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,应用程序必须保证这个条件变量未被使用。

反初始化

在释放底层的内存空间之前,可以使用pthread_cond_destroy函数对条件变量进行反初始化:

int pthread_cond_destroy(pthread_cond_t *cond);

使条件变量阻塞

条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量,然后调用下面函数等待条件变量为真。

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); //返回值:函数成功返回0;任何其他返回值都表示错误

函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cond参数指向的条件变量上。

被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。

pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。

阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:

pthread_mutex_lock();while (condition_is_false)    pthread_cond_wait();pthread_mutex_unlock();

阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

pthread_cond_timedwait则添加了一个超时值,如果超过到期时条件还是没有出现,则函数重新获取互斥量,然后返回ETIMEDOUT。

int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const structtimespec *restrict abstime);

解除阻塞

下面函数用于通知线程条件已经满足:

int pthread_cond_signal(pthread_cond_t *cond);  //返回值:函数成功返回0;任何其他返回值都表示错误

函数被用来释放被阻塞在指定条件变量上的一个线程。必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。

唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。

如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

int pthread_cond_broadcast(pthread_cond_t *cond); //返回值:函数成功返回0;任何其他返回值都表示错误

函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cond被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

#include <stdio.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t counter_lock;pthread_cond_t counter_nonzero;int counter = 0;int estatus = -1;void *decrement_counter(void *argv);void *increment_counter(void *argv);int main(int argc, char **argv){    printf("counter: %d\n", counter);    pthread_t thd1, thd2;    int ret;    ret = pthread_create(&thd1, NULL, decrement_counter, NULL);    if(ret){        perror("del:\n");        return 1;    }    ret = pthread_create(&thd2, NULL, increment_counter, NULL);    if(ret){        perror("inc: \n");        return 1;    }    int counter = 0;    while(counter != 10){        printf("counter(main): %d\n", counter);        sleep(1);        counter++;    }    return 0;}void *decrement_counter(void *argv){    printf("counter(decrement): %d\n", counter);    pthread_mutex_lock(&counter_lock);    while(counter == 0)        pthread_cond_wait(&counter_nonzero, &counter_lock); //进入阻塞(wait),等待激活(signal)    printf("counter--(before): %d\n", counter);        counter--; //等待signal激活后再执行    printf("counter--(after): %d\n", counter);        pthread_mutex_unlock(&counter_lock);     return &estatus;}void *increment_counter(void *argv){    printf("counter(increment): %d\n", counter);    pthread_mutex_lock(&counter_lock);    if(counter == 0)        pthread_cond_signal(&counter_nonzero); //激活(signal)阻塞(wait)的线程(先执行完signal线程,然后再执行wait线程)    printf("counter++(before): %d\n", counter);        counter++;     printf("counter++(after): %d\n", counter);        pthread_mutex_unlock(&counter_lock);    return &estatus;}

这里写图片描述

参考:
APUE第11章
http://www.cnblogs.com/Anker/archive/2012/12/18/2823684.html
http://www.cnblogs.com/runnyu/p/4643363.html
http://blog.csdn.net/ithomer/article/details/6031723

原创粉丝点击