Linux Day14 :线程的创建与同步

发布于:2023-09-14 ⋅ 阅读:(103) ⋅ 点赞:(0)

一、简单认知

进程:一个正在运行的程序

线程:进程内部的一个执行路径

头文件:#include<pthread.h>

二、进程与线程的区别

进程是资源分配的最小单位,线程是 CPU 调度的最小单位
进程有自己的独立地址空间,线程共享进程中的地址空间
进程的创建消耗资源大,线程的创建相对较小
进程的切换开销大,线程的切换开销相对较小

三、线程的实现方式

用户级:开销小,可以创建很多,但不能利用多处理器资源。就算你弄了多了线程,但在内核中你只有一个线程,尽管你有多个处理器,还是通过时间片轮转法来实现并发

内核级:开销大(相对用户级来说),由内核直接管理,可以利用多处理器资源。Linux采用的就是这种

组合:介于这三种之间

在操作系统中,线程的实现有以下三种方式:

◼ 用户级线程 :用户级线程是完全在用户空间中实现的线程。操作系统内核对其一无所知,只知道进程的存在。用户级线程的创建、调度和管理完全由用户级的线程库完成。由于这些操作不需要内核的介入,所以用户级线程的创建和切换比内核级线程更加快速、高效。然而,由于操作系统对用户级线程一无所知,因此一个阻塞的用户级线程会导致整个进程阻塞,这是用户级线程的一个主要缺点。

◼ 内核级线程 :内核级线程是直接由操作系统内核支持和管理的线程。内核维护了所有内核线程的上下文信息,并负责线程的调度和切换。因此,内核级线程可以利用多处理器并行性,同时,当一个内核级线程阻塞时,内核可以调度该进程的其他线程执行。然而,内核级线程的创建和切换需要进行用户态到内核态的切换,因此成本比用户级线程高。

◼ 组合级线程:组合级线程是用户级线程和内核级线程的组合,试图结合两者的优点。在这种模型中,一个用户级线程对应于一个或多个内核级线程。这样,即使一个用户级线程阻塞,也不会阻塞整个进程,因为内核可以调度对应的其他内核级线程执行。同时,用户级线程的创建和切换可以在用户空间内完成,避免了频繁的用户态到内核态的切换。

Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把
所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来
表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯
一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和
其他一些进程共享某些资源,如地址空间)。
使用ps -eLf以上图为例,进程id都是5620,但是有6个线程,其线程id从5620开始到5631( 所有的线程都当做进程来实现)

四、一些栗子

1、第一个小栗子

step1:不加睡眠函数

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void* fun(void* arg){
    for(int i=0;i<5;i++)
    {
        printf("fun run\n");
    }
}
int main()
{
    pthread_t  id;
    pthread_create(&id,NULL,fun,NULL);
    for(int i=0;i<5;i++)
    {
        printf("main run\n");
        //sleep(1);
    }
    exit(0);
}

结果:

一般是全是主函数,因为在调用线程函数的时候主函数已经执行完毕,Exit(0)了,所以不会打印fun函数

step 2:主函数加上睡眠函数

void* fun(void* arg){
    for(int i=0;i<5;i++)
    {
        printf("fun run\n");
    }
}
int main()
{
    pthread_t  id;
    pthread_create(&id,NULL,fun,NULL);
    for(int i=0;i<5;i++)
    {
        printf("main run\n");
        sleep(1);
    }
    exit(0);
}

2、第二个小栗子

循环打印所在位置

void*thread_fun(void*arg)
{
    int*p=(int*)arg;
    int index=*p;
    for(int i=0;i<3;i++)
    {
        printf("intdex=%d\n",index);
        sleep(1);
    }
}
int main()
{
    pthread_t id[5];
    for(int i=0;i<5;i++)
    {
        pthread_create(&id[i],NULL,thread_fun,(void*)&i);
    }
    for(int i=0;i<5;i++){
        pthread_join(&id[i],NULL);
    }
}

结果

问题:

这里将i的地址传给了fun()参数,解引用后就能得到i的值,这在单线程(一个内核)是没得问题的,但是多核多线程是有的,每个线程同时获取i的地址,但是线程启用是需要花费时间,等打印获取i的值时,那一刻的i不一定是你传入时期的i.

3.第三个小例子

将一个全局变量加到5000

#include<pthread.h>
int g_val=1;

void*fun(void* arg)
{
    for(int i=0;i<1000;i++)
    {
        printf("val=%d\n",g_val++);
    }
}
int main()
{
    pthread_t id[5];
    int i=0;
    for(;i<5;i++)
    {
        pthread_create(&id[i],NULL,fun,NULL);
    }
     for(i=0;i<5;i++)
    {
        pthread_join(id[i],NULL);
    }
    exit(0);
}

结果不到5000,如果只有一个处理器的话不会出现两个程序并行的情况,但是处理器大于2时,出现两个程序并行。

解决方案:

1.加信号量

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
int g_val=1;
sem_t sem;
void*fun(void* arg)
{
    for(int i=0;i<1000;i++)
    {
        sem_wait(&sem);
        printf("val=%d\n",g_val++);
        sem_post(&sem);
    }
}
int main()
{
    pthread_t id[5];
    sem_init(&sem,0,1);
    int i=0;
    for(;i<5;i++)
    {
        
        pthread_create(&id[i],NULL,fun,NULL);
        
    }
     for(i=0;i<5;i++)
    {
        pthread_join(id[i],NULL);
    }
    sem_destroy(&sem);
    exit(0);
}

2.加互斥锁

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
int g_val=1;
pthread_mutex_t mutex;
void*fun(void* arg)
{
    for(int i=0;i<1000;i++)
    {
        pthread_mutex_lock(&mutex);
        printf("val=%d\n",g_val++);
        pthread_mutex_unlock(&mutex);
    }
}
int main()
{
    pthread_t id[5];
    pthread_mutex_init(&mutex,NULL);
    int i=0;
    for(;i<5;i++)
    {
        
        pthread_create(&id[i],NULL,fun,NULL);
        
    }
     for(i=0;i<5;i++)
    {
        pthread_join(id[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
    exit(0);
}

留一下一道思考题轮流打印abcabc按照这个顺序,该如何处理


网站公告

今日签到

点亮在社区的每一天
去签到