多线程编程

多线程编程


一、什么是线程??? 
线程是在进程内部运行的控制流程。多线程的控制流程可以长期共存,操作系统会在各线程之间调度和切换,就像在多个进程之间调度和切换一样。 
由于同一个进程的多个线程共享同一地址空间地址空间,因此代码段和数据段都是共享的,如果定义一个函数,在各线程之中都可以调用,如果定义一个全局变量,在各线程中都可以访问到的,除此之外,线程还共享以下资源: 
1、文件描述符表 
2、每种信号的处理方式 
3、当前工作目录 
4、用户id和组id 
5、堆空间 
但是有些资源是每个线程各有一份的 
1、线程id 
2、上下文,包括各种寄存器的值、程序计数器和栈指针 
3、栈空间 
4、errno变量 
5、信号屏蔽字 
6、调度优先级 
下面介绍POSIX标准的的线程pthread。线程函数位与libpthread共享库中,由于使用了共享库因此在编译时要加上-lpthread选项。
二、线程的原理 
下面所介绍的是linux中线程的原理。回想一下我们所说的线程的概念,线程是一个控制流程,那么一个单执行流的进程是不是一个控制流程呢???答案是肯定的,一个单执行流的进程也是一个线程。严格来说linux中是没有线程的概念的,linux中的线程都是用进程模拟的,所以linux中的线程又叫做轻量级进程。 
我们知道,进程是操作系统分配资源的最小单位,每当内核中新创建一个PCB的时候,就表示内核中新创建了一个进程。如下图,内核会为新创建的进程分配一系列的数据结构用来管理进程。 
这里写图片描述
在linux中可以通过vfork()函数来创建一个子进程,在vfork分流之后、如果子进程中没有使用exec函数进行程序替换的话,则子进程会在父进程的地址空间中运行父进程的代码。其效果如图: 
这里写图片描述 
这时候PCB1和PCB就拥有相同的虚拟内存和相同的物理内存了,如果我们这时候再为PCB1创建一些他私有的资源(比如为他分配栈空间,给他独立出来一部分页表),那么PCB1就变成一个线程了。
三、线程的使用 
下面所介绍的函数都是POSIX标准制定的,所有函数都在线程共享库中,所以在使用的时候要引入头文件pthread.h。
1、线程的创建 
int pthread_create(pthread_t tid,const pthread_attr_t *attr,void (start_routine)(void),void *arg);
返回值:成功返回0,失败返回错误码。可以通过char *strerror(int errnum)函数提取错误信息。 
tid:用来保存线程的id。属于一个输出型参数,将线程的id保存起来。 
attr:用来修改线程的属性。如果为NULL的时候表示创建的线程是默认属性。 
start_routine:start_routine是一个函数指针,它指向一个返回值为void*,参数为void* 函数。该线程创建好了以后就会执行这个函数。当start_routine返回之后该线程就退出了。 
arg:是start_routine所指向函数的参数。
2、线程的终止 
线程的终止有三种方式: 
2.1、return 
使用return可以终止一个线程,但是如果是在main函数中调用return的话,相当于进程退出,那么所有线程都会退出,相当于调用exit或_exit函数。在任意一个线程中调用exit或_exit的话会使所有线程退出。
2.2、void pthread_exit(void *retval) 
在线程中调用pthread_exit()可以使线程自己退出。退出的信息可以通过参数传出去,被等待他的线程获取。
2.3、int pthread_cancel(pthread_t tid) 
在一个线程中调用pthread_cancel函数可以终止另一个线程。假设A线程是被pthread_cancel异常终止的,则pthread_join获取的线程A的退出码就是PTHREAD_CANCEL,这个宏的值是-1,可在pthread.h头文件中找到。 
返回值:成功返回0,失败返回错误码。 
tid:要终止的线程的id。
3、线程等待 
int pthread_join(pthread_t tid,void** retval); 
功能:用来等待线程退出,并回收该线程的退出信息以及释放该进程的资源。 
举个栗子:在主线程之中创建一个新线程,假如在主线程要退出的情况下,新线程还没有执行完,那么当主线程退出后新线程也会立即退出,但是程序的运行结果可能就是错的。所以在这种情况下,需要主线程去等待新线程。 
返回值:成功返回0,失败返回错误码。 
tid:要等待线程的id。 
retval:用来获取线程的退出信息。一般情况下,线程退出时不会立即释放它所占的所有资源,而是等待有人来获取它的退出信息之后才进行释放资源。否则会造成内存泄漏。
4、获取线程的id 
pthread_t pthread_self(); 
在一个线程中调用这个函数会返回该线程的id。
四、线程的可结合和可分离 
在任何一个时间点上,线程是可结合的或是可分离的。 
可结合: 
一个可结合的线程能够被其他线程回收资源和杀死。在被其他线程回收之前,它的存储资源是不释放的。 
可分离: 
一个可分离的线程是不能被其他线程回收或杀死的,它的存储器资源在他终止的时候由系统自动释放。一个可分离的线程是不能被等待的。 
int pthread_detach(pthread_t tid): 
这个函数可以将一个线程设置成可分离的。一个线程可以自己调用pthread_detach(pthread_self())将自己设置为可分离的。也可以通过别的线程将自己设置为可分离的。
默认情况下,线程被创建成可结合的。如果一个可结合的线程运行结束但没有被join,则它的状态类似于僵尸进程。也就是还有一部分资源没有被回收。 为了避免存储器泄漏,每一个可结合的线程都要么被显示地回收。即调用pthread_join等待进程运行结束,并可得到线程的退出码,回收其资源。或者是调用pthread_detach函数将线程分离。在调用pthread_join之后,如果该线程没有运行结束,则调用者会被阻塞。 
当主线程退出后,就相当于进程退出了,这时候不管是可结合还是可分离的进程都会被回收结束掉。
五、线程的基本特点 
1、进程是系统分配资源的基本单位,而线程是执行的基本单位。 
2、线程在他在进程的内部运行,在linux中轻量级进程有可能就是线程。 
3、由于进程之间代码段可数据段是共享的,所以线程间通信是很容易的。 
4、由于线程是在一个进程内部运行的,所以线程的pid和组id是相同的,但是线程的id是不同的。 
说到这,我们来介绍一条命令:ps -aL 
这里写图片描述 
5、一般情况下创建出来的线程的优先级都是平等的。 
6、进程强调独立,线程强调共享。
栗子:有一个全局变量count=0,创建两个线程分别对这个count自增5000。
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int count=0;
void * Counter(void *arg)
{
    int i=0;
    while(i<5000)
    {
        count++;
        i++;
    }    
    return NULL;
}


int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,Counter,NULL);
    pthread_create(&tid2,NULL,Counter,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("count=%d\n",count);
    return 0;
}
 count =10000; 
以现在CPU的速度而言,一般情况下这个代码的结果都是正确的。 


再看下面这个代码:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
int count=0;
void * Counter(void *arg)
{
    int i=0;
    int val=0;
    while(i<5000)
    {
        val=count;
        printf("thread id=%lu,count=%d\n",pthread_self(),val+1);
        i++;
    }    
    return NULL;
}


int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,Counter,NULL);
    pthread_create(&tid2,NULL,Counter,NULL);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    printf("count=%d\n",count);
    return 0;
}
count = 5104
这到底是怎么回事呢??? 
这就牵扯到线程间的同步与互斥了。对于第一份代码来说,由于现在cpu性能比较高,所以一个线程还没有被切换出去就被执行完了,所以不会破坏线程间的同步与互斥性。 
而对第二份代码来,由于while循环中的操作多了,还调用了printf函数(这势必会使得cpu从内核态切换到用户态),在这个过程中会将线程切出去,这过程中有可能就会导致一个线程还没有来得及将count写回内存,而另一个线程又从内存中读取值,这就造成了错误。 
有关这部分的内容,。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值