初学线程

线程概念

线程是进程中的⼀个执行单元,负责当前进程中程序的执行,⼀个进程中至少有⼀个线程
⼀个进程中是可以有多个线程
多个线程共享同一个进程的资源,每个线程参与操作系统的统一调度

进程相当于是由进程资源+主线程+子线程,组合而来

程序由进程进行执行,进程由线程执行

线程与进程区别

内存空间
          一个进程中多个线程共享同一个内存空间
          多个进程拥有独立的内存空间
进程/线程间通讯
        线程间通讯方式简单
        进程间通讯方式复杂

并发操作,线程比进程更节约资源

联系紧密的任务在并发时优先选择多线程,如果任务之间比较独立,在并发时建议选择多进程

 线程资源分配

共享进程的资源

        同一块地址空间

        文件描述符表

        每种信号的处理方式(如:SIG_DFL,SIG_IGN或者自定义的信号优先级)

        当前工作目录

        用户id和组id

独立的资源:

        线程栈

        每个线程都有私有的上下文信息。

        线程ID

        寄存器的值

        errno变量

        信号屏蔽字以及调度优先级

线程相关指令

在 Linux 系统有很多命令可以查看进程,例如 pidstat 、top 、ps ,也可以查看一个进程下的线程

1.pidstat

选项
-t:显示指定进程所关联的线程
-p:指定进程pid

2.top 命令

top 命令查看某一个进程下的线程,需要用到 -H 选项在结合 -p 指定 pid

3.ps命令

ps 命令结合 -T 选项就可以查看某个进程下所有线程

线程的创建

创建线程调用 pthread_create 函数

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *
(*start_routine) (void *), void *arg);

thread:线程ID变量指针
 attr:线程属性,默认属性可设置为NULL
 start_routine:线程执行函数
 arg:线程执行函数的参数

一旦子线程创建成功,则会被独立调度执行,并且与其他线程并发执行

pthread_t thread_id_a,thread_id_b;
        int result = pthread_create(&thread_id_a,NULL,do_thread_funA,NULL);

注意:在编译时需要链接 -lpthread[Compile and link with -pthread]

线程的退出

线程退出使用 pthread_exit 函数

函数原型 void pthread_exit(void *retval);

retval:线程返回值,通过指针传递

1.当主线程调用pthread_exit函数时,进程不会结束,也不会导致其他子线程退出
2. 任何线程调用exit函数会让进程结束

线程的等待

主线程需要等待子线程退出,并释放子线程资源

线程等待调用 pthread_join函数,会阻塞调用线程

int pthread_join(pthread_t thread, void **retval);

thread:线程 ID
retval:获取线程退出值的指针

// 线程执行函数
void* do_thread_function(void* args)
{
        printf("do thread ....\n");
        pthread_exit(NULL);
}
int main()
{
        pthread_t thread_id;
        int result = pthread_create(&thread_id,NULL,do_thread_function,NULL);
        if(result!=0)
       {
                fprintf(stderr,"pthread error:%s\n",strerror(result));
                exit(EXIT_FAILURE);
       }
        printf("thread id is %ld\n",thread_id);
        pthread_join(thread_id,NULL);
        return 0;
}

线程分离

线程分为可结合的可分离的

可结合
        可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源
        (如栈)是不释放的。
        线程创建的默认状态为可结合的,可以由其他线程调用 pthread_join函数等待子线程退出并
        释放相关资源

可分离
不能被其他线程回收或者杀死的,该线程的资源在它终止时由系统来释放

 pthread_detach 函数

int pthread_detach(pthread_t thread);

设置在线程退出后,由操作系统自动释放该线程的资源

注意:线程分离函数不会阻塞线程的执行

 多个线程的创建

创建多个线程时,一般由主线程统一创建,并等待释放资源或者分离线程,不要递归创建
1. 多个线程如果任务相同,则可以使用同一个线程执行函数
2. 多个线程如果任务不同,则可以使用不同的线程执行函数

// 线程执行函数
void* do_thread_funA(void* args)
{
        printf("do thread A\n");
        pthread_exit(NULL);
}
void* do_thread_funB(void* args)
{
        printf("do thread b\n");
        pthread_exit(NULL);
}

线程间通信

为什么需要线程通信?
线程是操作系统调度的最小单元,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信
线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺

主线程向子线程传递

 还记得pthread_create函数么

可以传很多不止int,如果你想传多种数据,可以创建结构体,传指针就行了

子线程向主线程传递

在子线程将需要返回的值存储在 pthread_exit 函数中的 retval 参数中
在主线程中通过 pthread_join 函数的第2个参数 retval 得到返回, pthread_join 函数会将线程的
返回值(指针)保存到 retval 中

线程同步

线程同步:是指在互斥的基础上,通过其它机制实现访问者对 资源的有序访问。
条件变量:线程库提供的专门针对线程同步的机制
线程同步比较典型的应用场合就是 生产者与消费者

线程互斥锁

线程的主要优势在于能够通过全局变量来共享信息,不过这种便捷的共享是有代价的:
1.必须确保多个线程不会同时修改同一变量
2.某一线程不会读取正由其他线程修改的变量,实际就是 不能让两个线程同时对临界区进行访问
3.线程互斥锁则可以用于解决多线程资源竞争问题

 举个例子

int global = 0;
//线程执行函数
void* do_thread(void* argv)
{
        //循环对global进行加1操作
        int loops = *(int*)argv;
        for(int i=0;i<loops;i++)
       {
                int temp = global;
                temp++;
                global=temp;
       }
        pthread_exit(NULL);
}
int main(int argc,char* argv[])
{
        if(argc !=2)
       {
                fprintf(stderr,"arguments must be 2:< cmd > <count>\n");
                exit(EXIT_FAILURE);
       }
        // 获取循环次数
        int loopCount = atoi(argv[1]);
        // 循环的方式创建两个线程
        pthread_t tids[2]={0};
        int err;
        for(int i=0;i<2;i++)
       {
                err = pthread_create(tids+i,NULL,do_thread,&loopCount);
                if(err!=0)
               {
                        fprintf(stderr,"pthread_create 
failed:%s\n",strerror(err));
                        exit(EXIT_FAILURE);
               }
       }
        pthread_join(tids[0],NULL);
        pthread_join(tids[1],NULL);
        // 打印全局变量
        printf("global=%d\n",global);
        return 0;
}

这里需要将数字字符串转换为整数 :int atoi(const char *nptr);

当循环多次后

线程之间资源的竞争会导致其出错

这时就要用到互斥锁

1. 线程互斥锁工作机制
        当线程A获得锁,另外一个线程B在获得锁时则会阻塞,直到线程A释放锁,线程B才会获得锁。
2. 线程互斥锁工作原理
        本质上是一个pthread_mutex_t类型的变量,假设名为 v
        当 v = 1,则表示当前临界资源可以竞争访问 ,得到互斥锁的线程则可以访问,此时 v = 0
        当 v = 0,则表示临界资源正在被某个线程访问,其他线程则需要等待
3. 线程互斥锁的特点
        互斥锁是一个 pthread_mutex_t类型的变量,就代表一个互斥锁
        如果两个线程访问的是同一个 pthread_mutex_t 变量,那么它们访问了同一个互斥锁
        对应的变量定义在 pthreadtypes.h 头文件中,是一个共用体中包含一个结构体

互斥锁的初始化

线程互斥锁的初始化方式主要分为两种
1. 静态初始化
定义 pthread_mutex_t 类型的变量,然后对其初始化为 PTHREAD_MUTEX_INITIALIZER
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER
2. 动态初始化
动态初始化主要涉及两个函数 pthread_mutex_init 函数 与 pthread_mutex_destroy 函数

 pthread_mutex_init

函数原型

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

mutex:线程互斥锁对象指针

attr:线程互斥锁属性

pthread_mutex_destroy

函数原型 
int pthread_mutex_destroy(pthread_mutex_t *mutex);

mutex:线程互斥锁指针

互斥锁的操作

线程互斥锁的操作主要分为 获取锁(lock) 与 释放锁(unlock)

获取锁的函数:pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);

mutex:线程互斥锁指针

释放锁的函数:pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);

解除互斥锁锁定状态,解除后,所有线程可以重新竞争锁

mutex:线程互斥锁对象的指针

静态

// 互斥锁静态初始化
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
//线程执行函数
void* do_thread(void* argv)
{
        //循环对global进行加1操作
        int loops = *(int*)argv;
        for(int i=0;i<loops;i++)
       {
                // 获取锁,一旦获取成功,则获取临界资源的访问资格,否则会阻塞当前线程
                pthread_mutex_lock(&mtx);
                int temp = global;
                temp++;
                global=temp;
                // 释放锁
                pthread_mutex_unlock(&mtx);
       }
        pthread_exit(NULL);
}

动态:

// 互斥锁变量
pthread_mutex_t mtx;
int main(int argc,char* argv[])
{
        if(argc !=2)
       {
                fprintf(stderr,"arguments must be 2:< cmd > <count>\n");
                exit(EXIT_FAILURE);
       }
        pthread_mutex_init(&mtx,NULL);
        // 获取循环次数
        int loopCount = atoi(argv[1]);
        // 循环的方式创建两个线程
        pthread_t tids[2]={0};
        int err;
        for(int i=0;i<2;i++)
       {
                err = pthread_create(tids+i,NULL,do_thread,&loopCount);
                if(err!=0)
               {
                        fprintf(stderr,"pthread_create 
failed:%s\n",strerror(err));
                        exit(EXIT_FAILURE);
               }
       }
        pthread_join(tids[0],NULL);
        pthread_join(tids[1],NULL);
        pthread_mutex_destroy(&mtx);
        return 0;
}

生产者消费者模型

1. 在这个模型中,包括生产者线程与消费者线程。通过线程来模拟多个线程同步的过程
2. 在这个模型中,需要以下组件
仓库 : 用于存储产品,一般作为共享资源
生产者线程 : 用于生产产品
消费者线程 : 用于消费产品
3. 原理
当仓库没有产品时,则消费者线程需要等待,直到有产品时才能消费
当仓库已经装满产品时,则生产者线程需要等待,直到消费者线程消费产品之后

生产者与消费者模型同步

基于互斥锁实现生产者与消费者模型实现

// 静态互斥锁
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int product_number=0;
void* thread_handle(void* arg)
{
        // 生产产品的数量
        int cnt = atoi((char*)arg);
        for(int i=1;i<=cnt;i++)
       {
                // 获取锁
                pthread_mutex_lock(&mutex);
                printf("thread[%ld] produces a product, the number of 
products is %d.\n",pthread_self(),++product_number);
                // 释放锁
                pthread_mutex_unlock(&mutex);
       }
        pthread_exit(NULL);
}
// command format[./a.out 1 2 3],1 2 3表示生产者线程生产的产品数量
int main(int argc,char* argv[])
{
        int total_of_production= 0;// 生产产品的数量
        int total_of_consumption=0;// 消费产品的数量
        if(argc < 2)
       {
                fprintf(stderr,"command argument: ./a.out <...product 
quantity>\n");
                exit(EXIT_FAILURE);
       }
        pthread_t tid;
        for(int i=1;i<argc;i++)
       {
                total_of_production += atoi(argv[i]);
                int err = pthread_create(&tid,NULL,thread_handle,
(void*)argv[i]);
                if(err > 0)
               {
                        fprintf(stderr,"pthread_create 
error:%s\b",strerror(err));
                        exit(EXIT_FAILURE);
               }
       }
        // 消费产品
        while(1)
       {
                pthread_mutex_lock(&mutex);
                while(product_number > 0)
               {
                        printf("consumption a product ,the number of products 
is %d.\n",--product_number);
                  total_of_consumption++;
                        sleep(1);
               }
                pthread_mutex_unlock(&mutex);
                if(total_of_production == total_of_consumption)
               {
                        break;
               }
       }
        return 0;
}

条件变量

上面示例代码的不足:
主线程(消费者线程)需要不断查询是否有产品可以消费,如果没有产品可以消费,也在运行程序,包括获
得互斥锁、判断条件、释放互斥锁,非常消耗 cpu 资源。
使用条件变量进行修改
条件变量 允许一个线程就某个共享变量的状态变化通知其他线程,并让其他线程等待这一通知

 

初始条件变量

条件变量的本质为 pthread_cond_t 类型的变量,其他线程可以阻塞在这个条件变量上,或者唤醒阻塞在这个条件变量上的线程。

条件变量的初始化分为静态初始化与动态初始化(与互斥锁相似)
1. 静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2. 动态初始化
pthread_cond_init 函数

        int pthread_cond_init(pthread_cond_t *restrict cond,const 
        pthread_condattr_t *restrict attr);

        cond:条件变量指针
        attr:条件变量属性

pthread_cond_destroy函数

        int pthread_cond_destroy(pthread_cond_t *cond);

 

1:消费者线程判断消费条件是否满足(仓库是否有产品),如果有产品则可以消费,然后解锁
2 :当条件满足时(仓库产品数量为0),则调用 pthread_cond_wait 函数,这个函数具体做的事
情如下:
在线程睡眠之前,对互斥锁解锁
让线程进入到睡眠状态
等待条件变量收到信号时,该函数重新竞争锁,并获取锁
3 :重新判断条件是否满足,如果满足,则继续调用 pthread_cond_wait 函数
4 :唤醒后,从 pthread_cond_wait 返回,条件不满足,则正常消费产品

5 :释放锁,整个过程结束

实现这个模型还需要几个函数

pthread_cond_wait

        int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict 
        mutex);

        函数功能 
        阻塞线程,等待唤醒
        函数参数
        cond:条件变量指针
        mutex:相关联的互斥锁指针

1.条件变量需要与互斥锁结合使用,先获得锁才能进行条件变量的操作
2.调用函数后会释放锁,并阻塞线程
3.一旦线程唤醒,需要重新竞争锁,重新获得锁之后,pthread_cond_wait 函数返回

pthread_cond_signa

        int pthread_cond_signal(pthread_cond_t *cond);
        函数功能 
        唤醒所有阻塞在某个条件变量上的线程

pthread_cond_broadcast函数

        int pthread_cond_broadcast(pthread_cond_t *cond);
        函数功能 
        唤醒所有阻塞在某个条件变量上的线程

pthread_cond_signal 函数主要适用等待线程都在执行完全相同的任务
pthread_cond_broadcast 函数主要适用等待线程都执行不相同的任务
条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制,发送信号时若无任何线
程在等待该条件变量,则会被忽略
条件变量代表是一个通讯机制,用于传递通知与等待通知,用户可以设定条件来发送或者等待通知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值