多线程-------初学者入门

进程:他是程序执行和资源分配的一个基本单元。每个进程都有独立的资源   代码段  数据段   堆栈段

线程:轻量级的进程。进程内的基本调度单位,同一进程内的线程共享进程的地址空间

进程就好比一个车间,线程就好比一个车间里的工人,为完成一个产品各自做自己的工作

线程的优点:

        线程占用的系统的资源比进程要少的多(占用资源少)
        线程的创建要比进程的创建要简单的多(创建简单)
        线程的创建的速度比进程的创建的速度要快很多(创建速度快)
        线程的切换的速度要比进程的切换的速度快的多(切换速度快)

线程的缺点:假如有一个线程异常退出    进程内所有的线程都会挂掉。 一死俱死

进程:空间独立、操作简单、浪费空间;       线程:通信简单、节约资源、不易控制

进程和线程的选择:

        1)需要频繁创建销毁的优先用线程。
        2)需要进行大量计算的优先使用线程。考虑切换时费时费力,影响计算效率
        3)强相关的处理用线程,弱相关的处理用进程
        4)因为对CPU系统的效率使用上线程更占优,可能要扩展到多机分布的用进程,多核分布的用线程
        5)都满足需求的情况下,用你最熟悉、最拿手的方式(工作中使用线程最多)

一、线程

多线程的调度次序:无法确定谁先执行  谁后执行

线程的函数不是linux系统的库函数,所有在编译的时候需要指定线程的库 -lpthread      例:gcc xxx.c -o xxx -lpthread

1、创建一个线程   pthread_create

        函数功能:创建一个线程

        函数的头文件:#include <pthread.h>

    函数的原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

        函数的参数:pthread_t *thread:线程号(要区分具体是哪一个线程)
                              const pthread_attr_t *attr:NULL(线程的默认属性 NULL)
                              void *(*start_routine) (void *):线程的执行体,即线程的实现函数
                              void *arg:传给线程的实现函数的参数  

        函数的返回值:成功返回 0                             失败返回 -1

#include<stdio.h>
#include<pthread.h>
void *myfunc(void *arg) //如果没有(void *)会报错
{

	printf("welcome to here!\n");
}

int main()
{
	pthread_t pd;
	pthread_create(&pd,NULL,myfunc,0);
	printf("hello world!\n");
	while(1);//如果主线程退出了,子线程就不执行了
	return 0;
}
//hello world!
//welcome to here!
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *func(void *data)//子线程
{
	printf("我是子线程!\n");
	printf("data:%d\n",*(int *)data);
}
int main()
{
	pthread_t mypthread;
	int num = 100;
	int ret;
	ret = pthread_create(&mypthread,NULL,func,(void *)&num);
	if(ret < 0)
	{
		printf("创建线程失败。\n");
		return -1;
	}
	printf("我是主线程!\n");
	sleep(1);
	return 0;
}
//我是主线程!
//我是子线程!
//data:100

2、获取自己的线程号   pthread_self

        函数功能:获取自己的线程号  

        函数的头文件:#include <pthread.h>

        函数原型:pthread_t pthread_self(void);

        函数的参数:无

        函数的返回值:线程的id号

#include<stdio.h>
#include<pthread.h>
void *myfunc(void *arg)//如果没有(void *)会报错
{

	printf("welcome to here!\n");
	printf("this is self pd:%ld\n",pthread_self());
}

int main()
{
	pthread_t pd;
	pthread_create(&pd,NULL,myfunc,0);
	printf("this is pd:%ld\n",pd);
	printf("hello world!\n");
	while(1);//如果主线程退出了,子线程就不执行了
	return 0;
}
//this is pd:140594625840896
//hello world!
//welcome to here!
//this is self pd:140594625840896

3、等待一个线程结束     pthread_join

主线程一但退出所有的子线程都会结束,所以在主线程将退出的时候,要等待所有的子线程运行结束才可以退出

        函数功能:类似于wait/waitpid              等待指定的子线程退出(函数会阻塞当前进程)

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_join(pthread_t thread, void **retval);

        函数的参数:pthread_t thread:要等待的线程号

                              void **retval:线程退出的状态        一般写  NULL

        函数的返回值:成功返回   0                  失败返回    error

#include<stdio.h>
#include<pthread.h>
void *myfunc(void *arg) //如果没有(void *)会报错
{

	printf("welcome to here!\n");
}

int main()
{
	pthread_t pd;
	pthread_create(&pd,NULL,myfunc,0);
	printf("hello world!\n");
	pthread_join(pd,NULL);
	return 0;
}
//hello world!
//welcome to here!

4、线程的退出       pthread_exit

        函数功能:线程的正常退出

        函数的头文件:#include <pthread.h>

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

        函数的参数:void *retval:退出的状态     一般写 NULL  或   0

        函数的返回值:无

#include<stdio.h>
#include<pthread.h>
void *myfunc(void *arg) //如果没有(void *)会报错
{
	printf("welcome to here!\n");
	pthread_exit(0);
	printf("this is test!\n");
}

int main()
{
	pthread_t pd;
	pthread_create(&pd,NULL,myfunc,0);
	printf("hello world!\n");
	pthread_join(pd,NULL);
	return 0;
}
//hello world!
//welcome to here!

5、取消一个线程的执行      pthread_cancel

        函数功能:取消一个线程          无论被取消的线程的状态如何都强制取消

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_cancel(pthread_t thread);

        函数的参数:pthread_t thread:要取消的线程号

        函数的返回值:成功返回  0                       失败返回 非零

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *fun_pthread(void *data)//子线程
{
	printf("data1:%d\n",*(int *)data);
	while(1)
	{
		sleep(1);
		printf("我是子线程\n");//每隔1秒,打印一句话,取消掉子进程后就不再打印
	}
	pthread_exit(0);//代表线程正常结束
}
int main()//主线程
{
	pthread_t pd;
	int num = 100;
	pthread_create(&pd,NULL,fun_pthread,(void *)&num);
	printf("我是主线程\n");
	sleep(5);
	pthread_cancel(pd);//5秒计时结束之后就取消子线程
	pthread_join(pd,NULL);//他会阻塞当前的线程
	printf("子线程已结束\n");
	return 0;
}
//我是主线程
//data1:100
//我是子线程
//我是子线程
//我是子线程
//我是子线程
//子线程已结束

6、退出清理函数   pthread_cleanup_push    pthread_cleanup_pop

线程退出的时候希望去执行某些函数,做一些工作,比如清理工作

        函数功能:类似于钩子函数                    退出清理函数

        函数头文件:#include <pthread.h>

        函数原型:void pthread_cleanup_push(void (*routine)(void*), void *arg);
                          void pthread_cleanup_pop(int execute);

        函数参数:void (*routine)(void*):回调函数的指针  就是清理函数你要做的事情

                          void *arg:传给回调函数的参数

                         int execute:0或者1             0表示不调用        1表示调用

pthread_cleanup_push是清理注册函数,就是利用这个函数去注册另一个函数,这个注册的函数需要做什么工作,你就直接写在函数体里就可以了。

注册函数注册成功之后,注册的这个函数是否执行由pthread_cleanup_pop函数的参数决定,当execute = 1的时候才会去执行注册的函数

这两个函数是联合使用的              注意:连续注册函数是遵循栈的规则,先进后出

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void func(void *arg)
{
	printf("this is a test\n");
}
void *myfunc(void *data)//子线程
{
	printf("data1:%d\n",*(int *)data);
	pthread_cleanup_push(func,NULL);
	pthread_cleanup_pop(1);
}
int main()//主线程
{
	pthread_t pd;
	int num = 100;
	pthread_create(&pd,NULL,myfunc,(void *)&num);
	printf("我是主线程\n");
	pthread_join(pd,NULL);//阻塞当前的线程,等待子进程结束后,再退出
	return 0;
}
//我是主线程
//data1:100
//this is a test

 有三种情况线程清理函数会被调用:
    线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
    线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
    线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.

//线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
#include <stdio.h>//这种情况下,对pthread_cleanup函数没有影响
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void mypush1(void *p)
{
	printf("mypush1:%d\n",*(int *)p);
}
void mypush2(void *p)
{
	printf("mypush2:%d\n",*(int *)p);
}
void mypush3(void *p)
{
	printf("mypush3:%d\n",*(int *)p);
}
void *fun_pthread(void *data)//子线程
{
	int i = 100;
	pthread_cleanup_push(mypush1,(void *)&i);//这里是注册一个函数
	pthread_cleanup_push(mypush2,(void *)&i);
	pthread_cleanup_push(mypush3,(void *)&i);
	sleep(100);//等待100秒后,再执行pthread_cleanup_pop函数
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(0);
	pthread_exit(0);//代表线程正常结束
}
int main()//主线程
{
	pthread_t pd;//创建的进程号
	int num = 100;
	pthread_create(&pd,NULL,fun_pthread,(void *)&num);
	printf("我是主线程\n");
	sleep(2);
	pthread_cancel(pd);//睡眠2秒后,取消正在执行的子线程
	pthread_join(pd,NULL);//他会阻塞当前的线程
	return 0;
}
//我是主线程
//mypush3:100
//mypush2:100
//mypush1:100
//线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
#include <stdio.h>//这种情况下,对pthread_cleanup函数没有影响
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void mypush1(void *p)
{
	printf("mypush1:%d\n",*(int *)p);
}
void mypush2(void *p)
{
	printf("mypush2:%d\n",*(int *)p);
}
void mypush3(void *p)
{
	printf("mypush3:%d\n",*(int *)p);
}
void *fun_pthread(void *data)//子线程
{
	int i = 100;
	pthread_cleanup_push(mypush1,(void *)&i);//这里是注册一个函数
	pthread_cleanup_push(mypush2,(void *)&i);
	pthread_cleanup_push(mypush3,(void *)&i);
	pthread_exit(0);//代表线程正常结束
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(0);
}
int main()//主线程
{
	pthread_t pd;//创建的进程号
	int num = 100;
	pthread_create(&pd,NULL,fun_pthread,(void *)&num);
	printf("我是主线程\n");
	pthread_join(pd,NULL);//他会阻塞当前的线程
	return 0;
}
//我是主线程
//mypush3:100
//mypush2:100
//mypush1:100

线程间的同步与互斥问题

因为一个进程中的各个线程是共用一个资源的,所以难免会出现资源抢占的问题,怎么避免他们出现资源抢占,这里有两种方法:一种是互斥锁,一种是信号量(条件变量、信号灯)。

所谓互斥锁就是对资源的一种保护,就是使用资源之前,加锁保护,一但你抢占成功之后并且加了锁,别人都不可使用了,一直等到你使用结束,别人才可以使用。

进行同步与互斥的目的:实现对资源的保护、独占

二、互斥锁(互斥信号量)

        需要安装库:sudo apt-get install manpages-posix-dev

        互斥锁的本质:就是一个结构体变量。  它的作用就是保护临界资源

        互斥的操作:在使用临界资源的时候  加锁;     使用完成临界资源的时候  解锁

        锁的常见的使用流程:①初始化锁,②上锁,③解锁,④销毁锁

        注意不要产生死锁:死锁就是在你没有解锁的时候,进行了二次加锁,就变成了死锁,那么这个资源就不能再使用了。

1、静态创建快速互斥锁      lock

静态锁:它是系统给你定义好了一个宏,然后你直接使用就可以了

        函数功能:静态创建快速互斥锁

        原型:pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;   //静态创建快速互斥锁

2、动态的创建一个互斥锁      pthread_mutex_init

动态锁:首先你要定义一个锁的变量,然后利用初始化函数进行变量的初始化,然后才可以对锁使用。

        函数功能:初始化一个互斥锁

        函数头文件:#include <pthread.h>

  函数的原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

        函数的参数:pthread_mutex_t *restrict mutex:互斥锁的结构体的指针
                             const pthread_mutexattr_t *restrict attr:锁的类型               这里写 NULL表示创建一个快速互斥锁

        函数的返回值:成功返回  0                      失败返回    error

3、互斥锁加锁      pthread_mutex_lock     pthread_mutex_trylock

        函数功能:互斥锁加锁

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
                              int pthread_mutex_trylock(pthread_mutex_t *mutex);

        函数的参数:pthread_mutex_t *mutex:互斥锁的结构体的指针

        函数的返回值:成功返回 0                           失败返回   -1

这两个函数的区别:pthread_mutex_lock:申请一个互斥锁   申请到就返回;申请不到  就阻塞
                                pthread_mutex_trylock:申请一个互斥锁  无论能不能申请到,都马上返回值,我们可以根据  返回值不同来判断  是否成功获取锁

4、互斥锁的解锁    pthread_mutex_unlock

        函数功能:互斥锁的解锁

        函数头文件:#include <pthread.h>

        函数的原型:int pthread_mutex_unlock(pthread_mutex_t *mutex)

        函数的参数:pthread_mutex_t *mutex:互斥锁的结构体的指针

        函数的返回值:成功返回 0                           失败返回  非零

5、 销毁一个互斥锁     pthread_mutex_destroy

        函数功能:销毁一个互斥锁

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_mutex_destroy(pthread_mutex_t *mutex)

        函数的参数:pthread_mutex_t *mutex:互斥锁的结构体的指针

        函数的返回值:成功返回  0                                 失败返回  非零

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

pthread_mutex_t mylock;//定义一个互斥锁的结构体

void *func(void * arg)
{	
	sleep(1);//保证让主线程先拿到锁
	pthread_mutex_lock(&mylock);
	int i=0;
	for(i=0;i<5;i++)
	{
		printf("i=%d\n",i);
		sleep(1);//间隔1秒打印一次
	}
	pthread_mutex_unlock(&mylock);
	pthread_exit(0);
}

int main()
{
	pthread_mutex_init(&mylock,NULL);
	pthread_t pd;
	pthread_create(&pd,NULL,func,0);
	pthread_mutex_lock(&mylock);
	int j=0;
	for(j=0;j<5;j++)
	{
		printf("j=%d\n",j);
		sleep(1);//间隔1秒打印一次
	}
	pthread_mutex_unlock(&mylock);
	pthread_join(pd,NULL);
	pthread_mutex_destroy(&mylock);
	return 0;
}
//j=0
//j=1
//j=2
//j=3
//j=4
//i=0
//i=1
//i=2
//i=3
//i=4

三、条件变量

条件变量依赖于锁的一种机制,他的用法跟互斥锁很类似,必须要有一把互斥锁

所谓的条件变量就是当满足一定的条件我就执行,不满足我就阻塞等待,等待条件满足的在运行。添加变量是和互斥锁共同使用的。

1、条件变量的创建

① 静态创建条件变量:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

② 动态创建条件变量:

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

        函数的参数:pthread_cond_t *restrict cond:条件变量的结构体的指针
                              const pthread_mutexattr_t *restrict attr:一般写 NULL

        函数的返回值:成功返回  0                              失败返回    非零

2、阻塞线程     pthread_cond_wait

        函数功能:满足不了条件则阻塞线程

        函数头文件:#include <pthread.h>

        函数的原型:int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

        函数的参数:pthread_cond_t *restrict cond:条件变量的结构体指针
                              pthread_mutex_t *restrict mutex:互斥锁的结构体的指针

        函数的返回值:成功返回  0                              失败返回   非零

3、解除阻塞       pthread_cond_signal

        函数功能:条件满足之后就解除当前的阻塞

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_cond_signal(pthread_cond_t *cond);

        函数的参数:pthread_cond_t *cond:条件变量的结构体指针

        函数的返回值:成功返回  0                     失败返回   非零

4、销毁条件变量     pthread_cond_destroy

        函数功能:销毁条件变量

        函数的头文件:#include <pthread.h>

        函数的原型:int pthread_cond_destroy(pthread_cond_t *cond)

        函数的参数:pthread_cond_t *cond:条件变量的结构体指针

        函数的返回值:成功返回  0                         失败返回  非零

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

pthread_cond_t  cond;
pthread_mutex_t  mylock;

void *func(void *arg)
{
	{
		pthread_cond_wait(&cond,&mylock);//只要有wait就立即阻塞
		printf("welcome to my space!\n");
	}
}	

void *func1(void *arg)
{
	sleep(1);//此函数延迟1秒,测试func函数是否先打印
	pthread_cond_signal(&cond);//1秒后,解除阻塞,func函数可以打印
	printf("here send over!\n");
}

int main()
{	
	pthread_cond_init(&cond,NULL);
	pthread_t pd,pd1;
	pthread_create(&pd,NULL,func,0);
	pthread_create(&pd1,NULL,func1,0);
	pthread_join(pd,NULL);
	pthread_join(pd1,NULL);
	pthread_cond_destroy(&cond);
	return 0;
}
//here send over!
//welcome to my space!

三、信号灯(信号量)

        信号灯属于线程的信号量。

        信号量   本质就是一个能进行加或者是减操作的一个数

        信号灯:本质上是一个非负的整数计数器,用来控制对公共资源的访问

       这个数我们可以对它进行加或者减操作,当我的这个数减到0的时候 ,表示资源没有了,陷入阻塞   必须等其他的线程释放了资源,我才能使用

原理:P 操作使 sem 减一,V 操作使sem 加一。信号量sem 的值大于零时,进程(或线程)具有公共资源的访问权限

1、信号灯的创建     sem_init

        函数功能:创建一个信号灯

        函数的头文件:#include <semaphore.h>

        函数的原型:int sem_init(sem_t *sem, int pshared, unsigned int value);

        函数参数:sem_t *sem:信号灯的结构体
                          int pshared:固定填 0
                          unsigned int value:信号量的初值         初值表示初始时可用资源的个数

        函数返回值:成功返回 0                          失败返回  非零

2、消耗一个信号灯   sem_wait     sem_trywait         

        函数功能:消耗一个信号量              P操作

        函数头文件:#include <semaphore.h>

        函数的原型:int sem_wait(sem_t *sem);         阻塞,申请不到信号量  就阻塞线程
                             int sem_trywait(sem_t *sem);     非阻塞,无论是否申请成功   都返回

        函数的参数:sem_t *sem:信号灯的结构体

        函数的返回值:成功返回  0                             失败返回  非零

3、释放一个信号灯    sem_post

        函数功能:释放一个信号灯               V操作

        函数的头文件:#include <semaphore.h>

        函数的原型:int sem_post(sem_t *sem);

        函数的参数:sem_t *sem:信号灯的结构体

        函数的返回值:成功返回  0                            失败返回  非零

4、销毁一个信号灯    sem_destroy

        函数功能:销毁一个信号灯

        函数头文件:#include <semaphore.h>

        函数原型:int sem_destroy(sem_t *sem);

        函数的参数:sem_t *sem:信号灯的结构体

        函数的返回值:成功返回 0                      失败返回  非零

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<semaphore.h>
#include<signal.h>
sem_t mysem;//定义一个信号灯结构体

void *func(void * arg)
{		
	printf("semid2:%d\n",sem_wait(&mysem));
	int i=0;
	for(i=0;i<5;i++)
	{
		printf("i=%d\n",i);
		sleep(1);
	}
	sem_post(&mysem);
	pthread_exit(0);
}

int main()
{
	sem_init(&mysem,0,1);//初值为1,表示初始时有1个可用资源,进程间需要竞争
	pthread_t pd;
	pthread_create(&pd,NULL,func,0);//当去寻找func函数时,主线程已经申请到了这个信号量,当主线程释放掉信号量之后,子线程才可以申请到这个信号量,所以j结束后,i才开始执行
	printf("semid:%d\n",sem_wait(&mysem));
	int j=0;
	for(j=0;j<5;j++)
	{
		printf("j=%d\n",j);
		sleep(1);
	}
	sem_post(&mysem);
	pthread_join(pd,NULL);
	sem_destroy(&mysem);
	return 0;
}
//semid:0
//j=0
//j=1
//j=2
//j=3
//j=4
//semid2:0
//i=0
//i=1
//i=2
//i=3
//i=4

附、向一个线程  发送一个信号    pthread_kill

        函数功能:向一个线程  发送一个信号,杀死的是线程所在的进程

        函数的头文件:#include <signal.h>

        函数的原型:int pthread_kill(pthread_t thread, int sig);

        函数的参数:pthread_t thread:线程号
                             int sig:发送的信号

        函数的返回值:成功返回 0                             失败返回   非零

线程间通讯的方式   主要有两种

第一种  全局变量

第二种  用pthread_kill

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值