操作系统之线程


线程
【本质】:线程就是在进程上下文中的一个执行流

基本概念
    进程:
        是操作调度运行的独立实体,它从main()函数开始,并按程序控制流顺序执行,
        通常情况下,这个执行过程一直持续到main()结束或遇到exit()为止,
        我们称这条【从开始到结束所构成的执行轨迹】为一个【执行流】。

    线程:
        1.线程是比进程更小的【活动单位】,它是进程中的一个执行流
        2.线程同进程内其它的线程共享进程地址空间    【线程间共享所在进程的地址空间】
        3.实现线程间的【通信十分方便】
        4.创建一个线程比创建一个进程【开销小】的多
        5.进程是系统分配资源的最小单位,【任务调度的最小单位是线程】
        6.线程不是标准C的一部分,需要额外的库才能实现线程
            执行生成新线程的文件时需要加 -pthread
        
        sudo apt-get install manpages-posix-dev         //安装线程所需环境

线程使用步骤及函数:
1.创建线程
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
    即:pthread_create(新线程的ID所在地址,新线程的属性,新线程的执行处理函数,线程函数的参数);
    返回值:成功返回0,失败返回错误码
    例如:
            pthread_t tid;    //定义一个变量用来存线程的ID
            pthread_create(&tid,NULL,thread_func1,NULL);
        &tid:存放新线程的ID的变量所在地址(用于唯一标识一个线程)
        NULL:指定新线程的属性(分离属性、栈空间、优先级等),
        一般为NULL,表示使用默认属性                                                                
        thread_func1:新线程所要执行处理的函数                                                                
        NULL:作为第三个参数的实参,即线程函数的参数,
        如果不需要参数,写NULL即可                                                                
                                                                        
2.退出线程        【这里要进行类型的强制转换】
    void pthread_exit(void *retval);                                                                    
    即: pthread_exit(线程的退出状态);    【这里要进行类型的强制转换】                                                                    
    功能:用于终止当下在调用的线程,通过参数返回当前线程的退出状态,
    可以使用同一进程中的其它线程调用pthread_join()函数来获取退出状态。
    例如:
            pthread_exit((void *)1);
        (void *)1:线程的退出状态
                                                                        
3.等待线程结束                                                                        
    int pthread_join(pthread_t thread, void **retval);
    即:pthread_join(要等待的线程的ID,线程的退出状态);                                                            
    功能:    等待指定的线程的结束才执行后面的程序                                                                
    返回值:    成功:返回0
                失败:返回错误码
    例如:
            pthread_join(tid,&retval);
        tid:要等待的线程的ID                                                                
        &retval:二级指针,用于获取目标线程的退出状态                                                                
                                                                        
【总结】:1.在创建线程时,若要传递参数给执行函数,就要把需要传递的    参数
            强制转换成    【(void *)】    型        而接收的时候执行函数
            要用    【void *参数名】        接收,调用的时候又要先定义一个想要的
            类型的变量,再将接收到的参数强制转换成    【想要的类型】    并赋值
            给该变量,执行时用该变量代替要传递的参数;【【要强制转换3次】】
            2.等待线程结束与退出线程必须一起出现才有意义,
            实际流程:    
            先在创建线程时定义【void *】型变量,用来存放线程的退出码
                    void *retval;    
            再在某线程的执行函数内结束该线程,并给他一个退出码作为参数
            【要强制转换成void * 型】
                    pthread_exit((void *)2000);                                                        
            最后在主调函数内等待该线程结束,并获取他的退出码
            【使用该退出码时要进行类型的强制转换】
                    pthread_join(tid,&retval);
                tid:要等待的线程的ID                                                                
                &retval:二级指针,用于获取目标线程的退出状态        
            当我执行        printf("retval 1 = %d\n",(int)retval);    时,
            会打印出我等待的ID为tid的线程的退出码;
        
互斥锁
【原理模拟】:                                                                        
int i=1;
if(i == 1)
{
    i--;    //加锁,执行P操作
    //执行任务;
    i++;    //解锁,执行V操作
}                                                                        
                                                                        
互斥锁使用步骤:
1.定义一个互斥锁
    pthread_mutex_t mutex;

2.初始化互斥锁【两种方案】
    【1】.
    int pthread_mutex_init(pthread_mutex_t * mutex,
              const pthread_mutexattr_t *attr);//第二个参数是属性,Linux没有实现,给NULL即可
    即:pthread_mutex_init(定义的互斥锁的地址,属性);
    例如:    
        pthread_mutex_init(&mutex,NULL);
    &mutex:定义的互斥锁的地址;
    NULL:属性,Linux没有实现,给NULL即可
    
    【2】.静态初始化    ,不需要提前定义      
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化

3.使用互斥锁对共享资源的访问进行加锁【两种方案】
    【1】.阻塞式
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    即:pthread_mutex_lock(定义的互斥锁的地址)
    功能:阻塞式的获取互斥锁【即若没有拿到锁,就一直等待而不返回去执行其他任务】
    例如:
        pthread_mutex_lock(&mutex);
    &mutex:定义的互斥锁的地址;
    
    【2】.非阻塞式
    int pthread_mutex_trylock(pthread_mutex_t *mutex);//非阻塞,即如果不能获得锁,就返回
    即:pthread_mutex_trylock(定义的互斥锁的地址);    
    功能:非阻塞式的获取互斥锁【即若没有拿到锁,就返回去执行其他的任务】
    例如:
        pthread_mutex_trylock(&mutex);
    &mutex:定义的互斥锁的地址;
    
4.访问共享资源

5.解锁
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    即:pthread_mutex_unlock(定义的互斥锁的地址);
    功能:解除互斥锁使其他线程可以访问该共享资源
    例如:
        pthread_mutex_unlock(&mutex);
    &mutex:定义的互斥锁的地址;

6.如果不再使用,删除互斥锁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    即:pthread_mutex_destroy(定义的互斥锁的地址);
    功能:删除互斥锁
    例如:
        pthread_mutex_destroy(&mutex);
    &mutex:定义的互斥锁的地址;                    
                    
信号量
/*********************
信号量就相当于共享内存中的一个盒子,
把信号量初始化成多少就是最开始的时候往盒子里放了多少个信号!
P操作就是从盒子里拿信号,只有盒子里有信号时才能拿,
若盒子里没有信号,你就只能在盒子旁边等到什么时候盒子里有信号了,拿到信号才能走!
V操作就是往盒子里放信号,不管盒子里有没有信号,你都可以往里面放信号!
*********************/
【本质】:信号量是在计算机中用来模拟信号灯的一个【整数】
当这个整数变为非0才能进行某种操作许可;
在进行操作的同时,将该整数减1,以此改变信号灯,
将减1操作称为P操作,也称获取信号;
当该操作进行完之后,将该整数加1,以此来恢复信号灯,
将加1操作称为V操作,也称释放信号;    
    
【原理】:可以模拟为一下代码:
int i=N;        //初始化信号量
if(i != 0)
{
    i--;    //可以进行P操作
    //i++    //也可以进行V操作
    进行某种操作.....
    i++;    //V操作
}
else    if(i == 0)
{
    i++;    //只能进行V操作
}

线程中信号量的使用步骤    
1.定义一个信号量
sem_t    sem;

2.初始化信号量    sem_init()函数
sem_init(&sem,0,1);
&sem:    定义的信号量的地址
第二个参数:0:表示同一进程中的线程间共享,非0:进程间共享
1:    信号量的初始值
注:sem处信号量为同一进程中线程共享的信号量,
信号的初始值为1,即P、V操作从1开始

3.获取信号量(即P操作)
sem_wait(&sem);
&sem:定义的信号量的地址

ps:访问共享资源

4.释放信号量(即V操作)
sem_post(&sem);
&sem:定义的信号量的地址

5.销毁信号量
sem_destroy(&sem);
&sem:定义的信号量的地址    
                                                                        
条件变量
http://www.cnblogs.com/feisky/archive/2010/03/08/1680950.html
【作用】:睡眠一个线程,等待某种条件出现再唤醒该线程
条件变量是【用来等待】而不是用来上锁的。条件变量用来自动阻塞一个线程,
直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。    
                
【原理】:条件变量是利用线程间【共享的全局变量】进行同步的一种机制
主要包括两个动作:
        【1】.一个线程等待"条件变量的条件成立"而挂起;
        【2】.另一个线程使"条件成立"(给出条件成立信号)。                        
条件的检测是在互斥锁的保护下进行的。
        如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。
        如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,
        重新获得互斥锁,重新评价条件。
            如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。                                
【组成】:    条件变量分为两部分: 条件和变量. 
        条件本身是由互斥量保护的. 线程在改变条件状态前先要锁住互斥量. 
        它利用线程间共享的全局变量进行同步的一种机制。                            
                                                                    
【执行流程】:        定义3个线程:1号、2号、3号
        3个线程先进行抢锁【发生抢锁事件】
        1.假设1号线程先得到锁,立马关锁
        此时其他线程进不来。
        
        2.1号线程碰到了条件变量
    a.条件为真:1号线程继续执行任务,执行完后开锁走人
        1号线程开锁后其他线程可以进来
    b.条件为假:
        b1.1号线程开锁【不是通过V操作开的锁,而是等待函数开的锁】并休眠等待
        此时锁是开着的,其他线程可以进来并把锁关上
        
        b2.假设2号线程过来打开了锁,并发出了唤醒信号唤醒休眠的1号线程
        此时1号线程要等待2号线程把锁打开才能再次判断条件变量是否为真
        b2.当2号线程执行完本次任务并开锁后,1号线程判断条件变量,进入2.1的分支
        b3.当1号线程条件为真,执行完本次任务并开锁后
        此时锁是开着的,任何线程都有可能抢到锁
        【发生抢锁事件】
【易错点】:
    1.任意进程开锁和关锁的次数是相等的
    2.当线程休眠时,会自动开锁,【不是通过V操作开的锁,而是等待函数开的锁】
    3.当休眠态被打破后,并不能直接进行判断,而是要在锁被打开时才能第一时间进行判断
    4.休眠态被打破后,要判断条件是否满足才能执行工作,不满足时还得继续开锁休眠
    5.【抢锁事件】只发生在【锁没有被打开】的情况下(即共享区无人)

条件变量使用步骤
    1.定义条件变量
        pthread_cond_t cond;

    2.初始化条件变量【两种方案】
    【1】.    
        int pthread_cond_init(pthread_cond_t * cond,
              const pthread_condattr_t * attr);
        即:pthread_cond_init(定义的条件变量的地址,属性);
        例如:    
                pthread_cond_init(&cond,NULL);
            &cond:定义的条件变量的地址
            NULL:属性,Linux没有实现,给NULL即可
        
    【2】.静态初始化        不需要提前定义  
        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
                                                                
    3.等待条件                            
        int pthread_cond_wait(pthread_cond_t * cond,
              pthread_mutex_t * mutex);                                                                    
        即:pthread_cond_wait(定义的条件变量的地址,条件);                                                                
        例如:
                pthread_cond_wait(&cond,&mutex);
            &cond:定义的条件变量的地址                                                            
            &mutex:条件,一般为    定义的互斥锁的地址                        

    4.唤醒等待条件满足的线程【两种方案】
    【1】.
        int pthread_cond_broadcast(pthread_cond_t *cond);
        即:pthread_cond_broadcast(定义的条件变量的地址);
        例如:
                pthread_cond_broadcast(&cond);
            &cond:定义的条件变量的地址        
            
    【2】.    
        int pthread_cond_signal(pthread_cond_t *cond);
        即:pthread_cond_signal(定义的条件变量的地址);
        例如:
                pthread_cond_signal(&cond);
            &cond:定义的条件变量的地址    
    
    5.销毁条件变量
        int pthread_cond_destroy(pthread_cond_t *cond);
        即:pthread_cond_destroy(定义的条件变量的地址);
        例如:
                pthread_cond_destroy(&cond);
            &cond:定义的条件变量的地址    条件变量中的【抢锁事件】实例:

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//定义互斥锁且初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//定义条件变量且初始化
int i = 1;
void * thread_func1(void*arg)//线程1
{
    for(i = 1; i < 10; i++)    //【去掉i++】
    {
        pthread_mutex_lock(&mutex);    //P操作
        printf("1 K\n");
        if(i%3 != 0)        
        {            //打印非3的倍数
            printf("thread1: %d\n",i);    //【i换成i++】
        }
        else if(i%3 == 0)
        {    //遇到3的倍数,则唤醒等待的进程
            pthread_cond_signal(&cond);
        }
        printf("1     G\n");
        pthread_mutex_unlock(&mutex);    //V操作
        sleep(1);
    }
}
void *thread_func2(void*arg)
{
    while(i < 10)
    {
        pthread_mutex_lock(&mutex);    //P操作
        printf("2         K\n");
        while(i%3 != 0)        //【条件变量】
        {        //若遇到的不是3的倍数
            pthread_cond_wait(&cond,&mutex);    //休眠,开锁,等待唤醒
        }
        printf("thread2: %d\n",i);        //【i换成i++】
        printf("2                 G\n");
        pthread_mutex_unlock(&mutex);    //V操作
        sleep(1);
    }
}
int main()
{
    int i;
    int res;
    pthread_t tid[2];    //定义两个变量用来存线程的ID
    res = pthread_create(&tid[0],NULL,thread_func1,NULL);
    //创建线程,将ID存入变量,该线程执行函数thread_func1
    //该线程属性默认,且不用向所执行的函数传递参数
    if(res != 0)
    {
        //...
    }
    printf("new thread id = %lu\n",tid[0]);
    //打印新线程的线程号
    res = pthread_create(&tid[1],NULL,thread_func2,NULL);
    //创建线程,将ID存入变量,该线程执行函数thread_func2
    //该线程属性默认,且不用向所执行的函数传递参数
    if(res != 0)
    {
        //...
    }
    printf("new thread id = %lu\n",tid[1]);
    //打印新线程的线程号
    for(i = 0; i < 2; i++)
    {
        pthread_join(tid[i],NULL);
        //等待线程
    }
    return 0;
}

/*******************************
运行结果及分析:
new thread id = 3076078400
new thread id = 3067685696
2         K                //2号开锁,条件不满足,关锁休眠等待
1 K                    //1号开锁,不发送唤醒信号,工作
thread1: 1
1     G                    //1号工作完关锁
1 K                    //1号开锁,不发送唤醒信号,工作
thread1: 2
1     G                //1号工作完关锁
1 K                    //1号开锁,发送唤醒信号,工作
1     G                //1号工作完关锁
thread2: 3        //条件满足2号工作
2                 G    //2号工作完关锁
!!!!发生抢锁事件,2号线抢到锁,开锁,判断,工作;此过程1号进不来
2         K            //2号开锁
thread2: 3        //2号工作
2                 G    //2号工作完关锁
!!!!发生抢锁事件,1号线抢到锁,开锁,不发信号,工作;此过程2号进不来
1 K                    //1号开锁,不发送唤醒信号,工作
thread1: 4
1     G            //1号工作完关锁
!!!!发生抢锁事件,2号线抢到锁,开锁,判断,关锁休眠;此过程1号进不来
2         K        //2号开锁,条件不满足,关锁休眠等待
1 K                    //1号开锁,不发送唤醒信号,工作
thread1: 5
1     G            //1号工作完关锁
1 K                    //1号开锁,送唤醒信号,2号被唤醒,1号继续工作
1     G            //1号工作完关锁
thread2: 6        //条件满足2号工作
2                 G        //2号工作完关锁
!!!!发生抢锁事件,2号线抢到锁,开锁,判断,工作;此过程1号进不来
2         K        //2号开锁
thread2: 6        //条件满足2号工作
2                 G    //2号工作完关锁
!!!!发生抢锁事件,1号线抢到锁,开锁,不发信号,工作;此过程2号进不来
1 K                    //1号开锁,不送唤醒信号,工作
thread1: 7
1     G            //1号工作完关锁
!!!!发生抢锁事件,2号线抢到锁,开锁,判断,关锁休眠;此过程1号进不来
2         K        //2号开锁,条件不满足,关锁休眠等待
1 K                    //1号开锁,不送唤醒信号,工作
thread1: 8
1     G            //1号工作完关锁
1 K                    //1号开锁,送唤醒信号,工作
1     G            //1号工作完关锁
thread2: 9    //条件满足2号工作
2                 G        //2号工作完关锁
!!!!发生抢锁事件,2号线抢到锁,开锁,判断,工作;此过程1号进不来
2         K        //2号开锁
thread2: 9    //条件满足2号工作
2                 G    //2号工作完关锁

避免抢锁的方案在【 】中

*****************************/


                    
                                                                        
                                                                        

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值