C语言——多线程


学习博文:

https://www.cnblogs.com/xiehongfeng100/p/4620852.html

"进程——资源分配的最小单位,线程——程序执行的最小单位"

线程概述

  • 父进程创建子进程会复制一份空间,类似于代码段->数据段->栈->堆-> 参数。

  • 一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。

  • 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

  • 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  • 从函数调用上来说,进程创建使用fork()操作;线程创建使用clone()操作。

线程创建以及退出

在这里插入图片描述

线程创建:

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
  • 第一个参数是指针指向线程,第二个是线程属性,第三个是相关的函数指针api,第四个是传参参数。
    当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
      新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

线程等待:

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号
  • 调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。

线程退出:

  • 单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流: 1)线程只是从启动例程中返回,返回值是线程的退出码。
    2)线程可以被同一进程中的其他线程取消。 3)线程调用pthread_exit:
#include <pthread.h>
int pthread_exit(void *rval_ptr);
#include <stdio.h>
#include <pthread.h>

void *fun(void *arg)
{
        static int ret = 666;//保证数据存在

        printf("t1:%ld create success!\n",(unsigned long)pthread_self());//打印pthread的pid号,返回值是pthread_t形
        printf("t1: param = %d\n",*((int *)arg));//打印传参的值

        pthread_exit((void *)&ret);
}

int main()
{
        int ret;
        int param = 520;
        pthread_t t1;

        int *pret;

        ret = pthread_create(&t1,NULL,fun,(void *)&param);

        if(ret == 0)
        {
                printf("main create success\n");
        }

        printf("main: %ld\n",(unsigned long)pthread_self());//dayin

        pthread_join(t1,(void **)&pret);//收回线程退出状态,需要指定线程调用pthread_exit。
//指针指向函数里的ret
        printf("main: t1 quit is %d\n",*pret);
        return 0;
}

在这里插入图片描述

共享内存的解释

线程对资源是争夺的,无法直接让线程谁先跑谁后跑。线程使用的都是同一个变量或者说位置。

线程同步之互斥量加锁

  • 创建及销毁互斥锁
  #include <pthread.h> 
  int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  //锁的指针地址以及锁的属性
  int  pthread_mutex_destroy(pthread_mutex_t mutex);//指针地址
   // 返回:若成功返回0,否则返回错误编号

要用默认的属性初始化互斥量,只需把attr设置为NULL。

  • 加锁及解锁
#include <pthread.h>
  int pthread_mutex_lock(pthread_mutex_t mutex *);
  //int pthread_mutex_trylock(pthread_mutex_t mutex *); 
  int pthread_mutex_unlock(pthread_mutex_t mutex *); 
  // 返回:若成功返回0,否则返回错误编号

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。

互斥锁限制共享资源访问

循环以及判断

死锁的解释

线程卡住,其他线程都在等待。比如:线程1锁住时准备锁住线程2,但是线程1进行时线程2也执行同时准备锁住线程,结果线程1运行想要锁住线程2,线程2运行想要锁住线程1,互相卡死。

线程条件控制实现线程同步

  • 创建及销毁条件变量
#include <pthread.h> 
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); 
int pthread_cond_destroy(pthread_cond_t cond); 
// 返回:若成功返回0,否则返回错误编号

除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL。

  • 等待
 #include <pthread.h> 
 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 
 //1:等待 ,条件    2:锁       
 int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout); 
   // 返回:若成功返回0,否则返回错误编号

pthread_cond_wait等待条件变为真。如果在给定的时间内条件不能满足,那么会生成一个代表一个出错码的返回变量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作都是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。
pthread_cond_timedwait函数的工作方式与pthread_cond_wait函数类似,只是多了一个timeout。timeout指定了等待的时间,它是通过timespec结构指定。

  • 触发
#include <pthread.h> 
int pthread_cond_signal(pthread_cond_t cond *); 
int pthread_cond_broadcast(pthread_cond_t cond *); 
//返回:若成功返回0,否则返回错误编号

这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
注意一定要在改变条件状态以后再给线程发信号。

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

int g_data = 0;

pthread_mutex_t mutex;
pthread_cond_t cond;

void *fun1(void *arg)
{
        printf("t1:%ld is create\n",(unsigned long)pthread_self());//pid
        printf("t1: get data is %d\n",*((int *)arg));
        static int num = 4;

        while(1)
        {
                pthread_cond_wait(&cond,&mutex);
                printf("t1 is begin >>>>>>>>>>\n");

                printf("t1 : %d \n",g_data);
                g_data = 0;
                sleep(1);
                if(num++ == 10){
                exit(1);}
        }
}

void *fun2(void *arg)
{
        printf("t2:%ld is create\n",(unsigned long)pthread_self());//pid
        printf("t2: get data is %d\n",*((int *)arg));

        while(1)
        {
                printf("t2: %d \n",g_data);
                pthread_mutex_lock(&mutex);
                g_data++;
                if(g_data == 3)
                {
                        pthread_cond_signal(&cond);
                }
                pthread_mutex_unlock(&mutex);
                sleep(1);
        }
}

int main()
{
        int ret;
        int pram = 666;
        pthread_t t1;
        pthread_t t2;

        pthread_mutex_init(&mutex,NULL);
        pthread_cond_init(&cond,NULL);

        pthread_create(&t1,NULL,fun1,(void *)&pram);
        pthread_create(&t2,NULL,fun2,(void *)&pram);

        pthread_join(&t1,NULL);//代替while(1)
        pthread_join(&t2,NULL);

        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);

        return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

llechee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值