Linux多线程开发

虚拟地址空间

  • 对于32位的机器,虚拟地址空间的大小为4G。0-3G为用户区,3-4G为内核区。也就是说,对于每一个进程,它们有独立的3G虚拟地址空间,还有1G的共享空间。
  • 用户区的组成:
    • 4k的受保护地址
    • 代码段(.text)和全局变量、静态局部变量
    • 堆空间存储new出来的数据
    • 内存映射段:用于装载动态共享库,如C标准库函数
    • 栈存储局部变量
    • 最后是一些命令行参数与环境变量

MMU

  • 将虚拟地址翻译成为物理地址
    虚拟地址空间显然会比物理内存空间大,也就是说页数大于页框数。执行指令时,如果使用的虚拟地址所在页已经被MMU映射到了物理内存空间,那么就没事,否则会产生缺页故障,随后将某页框的内容写入到磁盘来腾出空闲的页框。
  • 虚拟地址的组成
    对于一个16位的机器,虚拟地址空间的范围为0-64k,每页4k,则总共16页。因此16位虚拟地址的前4位用来作为页索引号,后12位用来表示页内偏移量。
    虚拟地址的组成

进程的三级映射

  • PCB 保存有指针 指向 一级页目录
  • 页目录 保存有指针指向 页表
  • 页表 保存有指针 指向物理页面

线程相关操作

  • 创建线程

    			//传出子线程ID			子线程函数(必须是void*)与参数
    pthread_create(pthread_t *thread,NULL,callback,(void *)&m);
    

    第三个参数pthread_attr_t是线程属性,可以使用一系列pthread_attr_xxx函数设置栈大小、禁止分离模式等。由此也可以认识到,同一进程下的线程尽管共享资源,但其栈是不共享的

    线程的标识ID 与 线程ID
    Linux中的线程包括 内核线程支持 和 用户线程库
    (线程ID)线程在内核中的唯一ID:pid_t
    pid_t gettid(void); syscall(SYS_gettid);

    (线程标识ID)用来描述同一进程中的不同线程的ID:pthread_t
    pthread_self获取的是相对于进程的线程控制块的首地址,当一个线程退出后,新创建的线程可以复用原来的pthread_t id。

    判断是否为同一个线程
    一个进程可以有多个独立的线程组,不同线程组间的线程是相互隔离的。因此,不能用pthread_t相等来判断它们是同一个线程,可能它们属于不同线程组,可以使用pthread_equal(pthread_t t1, pthread_t t2)来判断是否为同一个线程。

    总结:进程创建后,在其虚拟地址空间的mmap区域分配线程栈的内存空间,使用pthread_create创建线程得到的pthread_t用于用户空间管理线程,区分同一进程下的不同线程,方便用户使用用户态的库来操作线程。而实际上,每个线程在内核都有真正的线程id被称为内核线程ID,内核线程ID用于内核对线程的管理调度。

  • 终止线程

    pthread_exit(NULL);
    
  • 获取当前线程ID

    printf("子线程的线程id = %ld\n",pthread_self())
    
  • 阻塞等待指定线程结束才继续执行

    pthread_join(线程ID,NULL);
    

    如果线程使用pthread_exit((void *)m)结束的,那么pthread_join()的第二个参数传出变量m,只需对其强转为原来的类型即可。

  • 线程分离:该线程结束后自动归还资源

    pthread_detach(线程ID);
    

互斥量

为什么需要互斥锁:因为同一进程下的线程共享同一份全局内存区域,为了避免访问冲突,需要使用互斥锁来进行同步。

  • 创建:静态互斥与动态互斥

    • 静态互斥:

      pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
      
    • 动态互斥

      pthread_mutex_t mutex;
      //要使用时才初始化:
      pthread_mutex_init(&mutex, NULL);
      //不再使用该共享资源时,需要手动销毁锁:
      pthread_mutex_destroy(&mutex);
      

      这里的NULL是互斥锁属性(pthread_mutexattr_t),默认非递归、不支持错误检查和超时功能

  • 加锁、解锁

    pthread_mutex_lock(&mutex);
    pthread_mutex_unlock(&mutex);
    
  • 测试程序:主线程先执行,直到i<5,然后子线程执行

    静态互斥版:

    #include <pthread.h> 
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int i=8;//临界资源
    
    //1.创建静态互斥锁
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 
    
    //子线程
    void* childthread_fun()
    {
    
        while(i<=8)
        {
            pthread_mutex_lock(&mutex);
            //临界区/
            printf("子线程:%d\n", i);
            sleep(1);
            i++;
            printf("子线程:%d\n\n", i);
            //临界区/
            pthread_mutex_unlock(&mutex);
        }
    }
    
    //主线程
    int main() {
        //2.创建子线程
        pthread_t zid;
        pthread_create(&zid, NULL, childthread_fun, NULL);
        //3.主线程继续
        while(i>5)
        {
            pthread_mutex_lock(&mutex);
            //临界区/
            printf("主线程:%d\n", i);
            sleep(1);
            i--;
            printf("主线程:%d\n\n", i);
            //临界区/
            pthread_mutex_unlock(&mutex);
        }
        //主线程阻塞等待子线程结束,否则主线程一结束,子线程也就被结束了
        pthread_join(zid,NULL);
    }
    

    动态互斥版:

    #include <pthread.h> 
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int i=8;//临界资源
    
    //1.创建动态互斥锁,要在使用时才初始化
    pthread_mutex_t mutex; 
    
    //子线程
    void* childthread_fun()
    {
        while(i<=8)
        {
            pthread_mutex_lock(&mutex);
            //临界区/
            printf("子线程:%d\n", i);
            sleep(1);
            i++;
            printf("子线程:%d\n\n", i);
            //临界区/
            pthread_mutex_unlock(&mutex);
        }
    }
    
    //主线程
    int main() {
        //1.动态互斥锁
        pthread_mutex_init(&mutex, NULL);
    
        //2.创建子线程
        pthread_t zid;
        pthread_create(&zid, NULL, childthread_fun, NULL);
        //3.主线程继续
        while(i>5)
        {
            pthread_mutex_lock(&mutex);
            //临界区/
            printf("主线程:%d\n", i);
            sleep(1);
            i--;
            printf("主线程:%d\n\n", i);
            //临界区/
            pthread_mutex_unlock(&mutex);
        }
        //主线程阻塞等待子线程结束,否则主线程一结束,子线程也就被结束了
        pthread_join(zid,NULL);
        //4.销毁互斥锁
        pthread_mutex_destroy(&mutex);
    }
    

读写锁

什么是读写锁:是一把锁,读共享,写独占,读写不同时,优先写。
当A线程已经加读锁成功,线程B想加写锁,同时线程C想加读锁:由于读写不同时,因此线程B和C都阻塞。当线程A解锁后,由于优先写,线程B加写锁成功,而线程C继续阻塞。

加读锁:pthread_rwlock_rdlock(&rwlock);
加写锁:pthread_rwlock_wrlock(&rwlock);
解锁:pthread_rwlock_unlock(&rwlock);
  • 测试程序:

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <pthread.h>
    //1.定义一把读写锁
    pthread_rwlock_t rwlock;
    int number = 10;//共享资源
    //子线程的函数
    void *child_read()
    {
        while (1)
        {
            //加读锁
            pthread_rwlock_rdlock(&rwlock);
            printf("当前线程ID:%ld ,读number=%d\n", pthread_self(), number);
            //解锁
            pthread_rwlock_unlock(&rwlock);
            sleep(1);
        }
    }
    void *child_write()
    {
        while (1)
        {
            //加写锁
            pthread_rwlock_wrlock(&rwlock);
            number++;
            printf("当前线程ID:%ld ,写number=%d\n", pthread_self(), number);
            //解锁
            pthread_rwlock_unlock(&rwlock);
            sleep(1);
        }
    }
    
    //主线程
    int main()
    {
        //2.创建3个子线程:2个读,1个写
        pthread_t t[3];
        pthread_create(&t[0], NULL, child_read, NULL);
        pthread_create(&t[1], NULL, child_read, NULL);
        pthread_create(&t[2], NULL, child_write, NULL);
        //3.阻塞等待子线程结束
        for (int i = 0; i < sizeof(t)/sizeof(t[0]); i++)
        {
            pthread_join(t[i], NULL);
        }
        return 0;
    }
    

条件变量

线程A要满足条件cond才能继续执行,线程B可以提供这种条件。为了避免冲突,线程A与B访问资源时使用互斥锁。
在此情况下,如果线程A先抢到cpu:先加锁,发现不满足条件会马上解锁,然后阻塞等待线程B。
因此:这里的阻塞等待函数不仅需要传入条件变量,还需要传入互斥锁

  • 创建:也有静态和动态两种方式

    • 静态
      pthread_cond_t cond=PTHREAD_COND_INITIALIZER; 
      
    • 动态
      pthread_cond_t count_lock;
      //使用时才初始化:
      pthread_cond_init(&count_lock, NULL);
      //注销:
      pthread_cond_destroy(&count_lock) ;
      
  • 等待:

    • 一直阻塞等待,等待其它线程来唤醒
      pthread_cond_wait(&cond, &mutex);
      
    • 等待一段时间
      pthread_cond_timedwait(&cond, &mutex,const struct timespec   *abstime);
      
  • 唤醒

    • 唤醒在该条件变量上等待的一个线程
      pthread_cond_signal(&cond);
      
    • 唤醒在该条件变量上等待的全部线程
      pthread_cond_broadcast(&cond);
      
  • 测试程序

#include <pthread.h> 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int number=0;//资源(条件)
//1.互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//2.条件变量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER; 

void *shengchan()
{
    while (1)
    {
        //访问临界资源时要使用互斥锁
        pthread_mutex_lock(&mutex);

        number++;
        printf("生产,number=%d\n",number);
        if(number >= 4)
        {
            pthread_cond_signal(&cond);//唤醒消费者
        }

        pthread_mutex_unlock(&mutex);

        sleep(1);
    }
    
}

void *xiaofei()
{
    while (1)
    {
        //访问临界资源时要使用互斥锁
        pthread_mutex_lock(&mutex);

        if(number<3)//资源不足,就休眠等待
        {
            pthread_cond_wait(&cond, &mutex);//这里发现需要阻塞时会自动解除互斥锁
            printf("消费者被唤醒\n");
        }
        else
        {
            number = number - 3;
            printf("消费后,number=%d\n", number);
        } 
        pthread_mutex_unlock(&mutex);

        sleep(1);
    }
    
}


int main()
{
    //3.创建线程
    pthread_t p[2];//2个子线程
    pthread_create(&p[0], NULL, shengchan, NULL);
    pthread_create(&p[1], NULL, xiaofei, NULL);

    //4.等待子线程结束
    pthread_join(p[0], NULL);
    pthread_join(p[1], NULL);

    return 0;
}

信号量

初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared参数表示信号量是否可以在进程之间共享(0/1)
value是信号量的初始值
销毁信号量
int sem_destroy(sem_t *sem);

为了实现生产者与消费者的并行,将资源划分为空间资源与数据资源。
生产者:只关注空间资源,只要还有空间剩余,那就继续生产
消费者:只关注数据资源

假设空间资源为 block_sem , 数据资源为 data_sem
#include <iostream>
#include <pthread.h>
#include <unistd.h>    
#include <semaphore.h>
using namespace std;

sem_t block_sem, data_sem;//空间资源 数据资源

void *shengchan(void *)
{
    while (1)
    {
        //空间资源-1 数据资源+1
        sem_wait(&block_sem);
        //......生产.....
        cout<<pthread_self()<<"生产中"<<endl;

        sem_post(&data_sem);
        sleep(1);
    }
}
void *xiaofei(void *)
{
    while (1)
    {
        //数据资源-1 空间资源+1
        sem_wait(&data_sem);
        //......消费.....
        cout<<pthread_self()<<"消费中"<<endl;

        sem_post(&block_sem);
        sleep(1);
    }
}

int main()
{
    //1.初始化信号量
    sem_init(&block_sem, 0, 5);
    sem_init(&data_sem, 0, 0);
    //2.创建线程
    pthread_t p[2];
    pthread_create(&p[0], NULL, shengchan, NULL);
    pthread_create(&p[1], NULL, xiaofei, NULL);
    //3.等待线程结束
    pthread_join(p[0], NULL);
    pthread_join(p[1], NULL);
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值