导航

多线程

Posted on 2023-06-16 20:21  koodu  阅读(20)  评论(0编辑  收藏  举报

多线程

线程介绍

每个进程都会有一个主线程,在创建进程时创建,往后创建的线程都属于子线程;线程在进程里不断抢占运行时间片;当进程遇到return 结束,所有的线程全部结束。

线程分类

线程主要分为用户级线程和内核级线程

用户级线程主要解决上下文切换问题,其调度由用户控制

内核级线程由内核调度机制实现

当CPU分配给线程的时间片用完后,线程还未执行完毕,线程会从运行态转为阻塞态,将CPU让给其他线程使用。

线程的一些实现

在Linux,线程使用pthread线程库,编译时要加上 -lpthread

每个线程都有自己的线程标识id,pthread_t是线程的数据类型

线程创建

关于线程属性,一般在网络编程上,设置为分离属性,结束后线程自动释放。

!在64位操作系统上,创建线程,当线程进入执行函数后,传递的参数void* arg中,大小是8字节,与4字节int不匹配

线程终止

pthread_exit((void*)0);线程自动退出,同时返回0

在线程执行函数中使用return,也是线程自动退出

pthread_cancel(tid),tid线程被动终止

pthread_join(th,void**return);//等待th线程退出,同时接收其返回值。同时该函数是阻塞的,会一直等待,直到线程结束释放

pthread_join获得pthread_exit()返回的参数

二级指针把获得的值只进行一次解引用赋值,最后,result变量存的值就是线程返回的值

线程清理函数

线程与进程

线程状态

线程属性

线程属性要初始化,用完后要删除

设置线程属性及获取线程属性
#include<pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t* attr,int * detachstate);//获取当前线程属性的分离值
int pthread_attr_setdetachstate(const pthread_attr_t* attr,int detachstate);
成功返回0,出错返回出错错误编号
detachstate取值:
PTHREAD_CREATE_JOINABLE(默认)   正常启动线程
PTHREAD_CREATE_DETACHED        以分离状态启动线程

以分离状态启动的线程,不需要pthread_join()阻塞等待子线程结束释放资源,子线程结束后会自动释放资源

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
void outstate(pthread_attr_t *attr)//获取线程属性的分离值
{
	int state;//存储分离值
	if(pthread_attr_getdetachstate(attr,&state)!=0)//通过函数获取
	{
		perror("getdetachstat error");
	}else
	{
		if(state==PTHREAD_CREATE_JOINABLE) puts("默认");
		else if(state==PTHREAD_CREATE_DETACHED) puts("分离");
		else puts("获取失败");
	}
}
void* th_fun(void* arg)//线程执行函数
{
	long sum=0;
	for(int i=0;i<100;i++)
		sum+=i;
	return (void*)sum;
}
int main()
{
	pthread_t default_th,detach_th;//定义线程
	
	pthread_attr_t attr;//声明线程属性

	pthread_attr_init(&attr);//初始化线程属性
	outstate(&attr);//获取线程分离值

	int err;
	//以线程属性默认值,正常启动线程
	if((err=pthread_create(&default_th,&attr,th_fun,(void*)0))!=0)
	{
		perror("启动失败");
	}
	int res;
	if((err=pthread_join(default_th,(void*)&res))!=0)
	{
		perror("阻塞失败");
	}else
	{
		printf("res%d\n",(int)res);
	}
	puts("-------------------------------------------------------------");
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置线程属性的分离状态为分离
	outstate(&attr);
	if((err=pthread_create(&detach_th,&attr,th_fun,(void*)0))!=0)
	{
		perror("启动失败");
	}
	if((err=pthread_join(detach_th,(void*)&res))!=0)
	{
		perror("阻塞失败");
	}else
	{
		printf("res%d\n",(int)res);
	}
	puts("-------------------------------------------------------------");
	pthread_attr_destroy(&attr);//最后要销毁线程属性
	printf("%lx\n",pthread_self());
	return 0;
}

线程互斥和同步

线程同步主要有同步变量和线程信号量

线程互斥主要有互斥锁,读写锁,线程信号量

assert(int experence) 如果括号里为true则不执行任何;如果括号里为false则显示信息并终止程序
线程互斥
互斥锁

主要通过互斥锁实现pthread_mutex_t

互斥锁属性

设置互斥锁类型的实例:

互斥锁创建的一些步骤

互斥锁:
定义互斥锁的属性
初始化互斥锁属性
可以更改互斥锁的某些属性
定义互斥锁
初始化互斥锁,函数参数传入互斥锁,互斥锁的属性结构体
加锁、解锁
删除互斥锁的属性结构体
删除互斥锁

读写锁

对于互斥锁,对于临界资源的访问,都要加锁,缺乏读并发性,对于一些不会修改临界资源的线程,可以不用加互斥锁,考虑加读锁,多个线程对资源的访问加读锁不互斥

pthread_rwlock_t  都是读锁,不会发生互斥
读、读 不阻塞
读、写 阻塞
写、读 阻塞
写、写 阻塞

线程同步——条件变量

对于互斥锁,只要两种状态:锁定和非锁定

线程进入条件变量等待,要加互斥锁,条件变量是共享资源

条件变量的pthread_cond_wait()函数

条件满足后通知

线程同步的例子
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<time.h>
typedef struct//两个线程共享的结构体资源
{
	int res;
	int is_wait;//用户给出用于判断的条件,判断线程是否去占用资源
	pthread_cond_t cond;//条件变量
	pthread_mutex_t mutex;//互斥锁,限制条件变量
}Result;
void* set_fn(void* arg)//计算
{
	int i=1,sum=0;
	for(;i<=100;i++)
		sum+=i;//将计算结果放到共享结构体
	Result* r=(Result*)arg;
	r->res=sum;

	pthread_mutex_lock(&r->mutex);//对两个线程共享的判断条件加锁,每次只能一个线程访问
	while(!r->is_wait)//得到结果的线程没准备好
	{
		pthread_mutex_unlock(&r->mutex);//释放让另一线程准备
		usleep(100);
		pthread_mutex_lock(&r->mutex);//加锁判断
	}
	pthread_mutex_unlock(&r->mutex);

	//已经准备好了
	//唤醒等待结果的线程
	pthread_cond_broadcast(&r->cond);
	return (void*)0;
}
void* get_fn(void* arg)//获取结果
{
	Result* r=(Result*)arg;
	pthread_mutex_lock(&r->mutex);//加锁修改准备
	r->is_wait=1;//已准备好,进入等待队列

	//准备好后,就进入等待,然后就释放锁,让set_fn函数能获得锁,接着又加锁,进入队列,释放锁,wait等待阻塞
	pthread_cond_wait(&r->cond,&r->mutex);

	pthread_mutex_unlock(&r->mutex);
	int res=r->res;
	printf("结果%d\n",res);
	return (void*)0;
}
int main()
{
	int err;
	pthread_t cal,get;

	Result r;//定义共享资源
	r.is_wait=0;
	pthread_cond_init(&r.cond,NULL);//初始化共享资源里的锁,条件变量
	pthread_mutex_init(&r.mutex,NULL);

	if((err=pthread_create(&cal,NULL,set_fn,(void*)&r))!=0)//启动计算线程,进入set_fn函数,参数为共享结构体
	{
		perror("pthread set error");
	}
	if((err=pthread_create(&get,NULL,get_fn,(void*)&r))!=0)//启动结果线程,进入get_fn函数,参数为共享结构体
	{
		perror("pthread get error");
	}

	pthread_join(cal,NULL);//阻塞
	pthread_join(get,NULL);

	pthread_cond_destroy(&r.cond);//销毁
	pthread_mutex_destroy(&r.mutex);
	return 0;
}
读者写者(写者写后通知读者,读者读后通知写者)
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
typedef struct//共享资源
{
	int value;
	pthread_cond_t rc;//读者条件变量
	pthread_mutex_t rm;//读者锁
	int r_wait;//判断读者是否去占用共享资源读

	pthread_cond_t wc;//写者条件变量
	pthread_mutex_t wm;//写者锁
	int w_wait;//判断写者是否占用共享资源去写
}storage;
void set_data(storage* s,int value)
{
	s->value=value;
}
int get_data(storage*s)
{
	return s->value;
}
void* set_fn(void* arg)//写者:判断读者是否准备好读,唤醒,读完后写者准备好写,等待读者读完唤醒写
{
	storage* s=(storage*)arg;
    int i=1;
	for(;i<=10;i++)
	{
		set_data(s,i);//写入
		printf("0x%lx write data:%d\n",pthread_self(),i);
		pthread_mutex_lock(&s->rm);//加读者锁判断读者是否准备好读
		while(!s->r_wait)
		{
			pthread_mutex_unlock(&s->rm);
			usleep(100);
			pthread_mutex_lock(&s->rm);
		}
		s->r_wait=0;//读者读后置为0
        pthread_mutex_unlock(&s->rm);
		//唤醒读者读,读者在写者等待队列里
		pthread_cond_broadcast(&s->wc);

		//加锁准备好写
		pthread_mutex_lock(&s->wm);
		s->w_wait=1;
		pthread_cond_wait(&s->rc,&s->wm);//等待读者唤醒写,写者进入读者等待队列
		//释放写者锁?
		pthread_mutex_unlock(&s->wm);
	}
	return (void*)0;
}
void* get_fn(void* arg)//读者:准备好读,wait等待写者唤醒读,判断写者是否准备好写,唤醒
{
	storage* s=(storage*)arg;
	int i=1;
	for(;i<=10;i++)
	{
		pthread_mutex_lock(&s->rm);//加读者锁,准备
		s->r_wait=1;
		pthread_cond_wait(&s->wc,&s->rm);//读者等待,放到写者等待队列
		pthread_mutex_unlock(&s->rm);
		printf("0x%lx readyer data:%d\n",pthread_self(),get_data(s));//读取数据

		pthread_mutex_lock(&s->wm);//加写者锁判断写者是否准备好写
		while(!s->w_wait)
		{
			pthread_mutex_unlock(&s->wm);
			usleep(100);
			pthread_mutex_lock(&s->wm);
		}
		s->w_wait=0;//写者写后置为0
        pthread_mutex_unlock(&s->wm);
		//唤醒写者写,写者在读者的等待队列,等待读者读完唤醒
		pthread_cond_broadcast(&s->rc);
	}
	return (void*)0;
}
int main()
{
	int err;
	pthread_t rth,wth;
	storage s;//定义共享资源
	//初始化条件变量,锁
	pthread_cond_init(&s.rc,NULL);
	pthread_cond_init(&s.wc,NULL);
	pthread_mutex_init(&s.rm,NULL);
	pthread_mutex_init(&s.wm,NULL);
	s.r_wait=0;
	s.w_wait=0;

	if((err=pthread_create(&rth,NULL,set_fn,(void*)&s))!=0)//读者线程
		perror("ready thread faile");
	if((err=pthread_create(&wth,NULL,get_fn,(void*)&s))!=0)//写者线程
		perror("writh thread faile");

	pthread_join(rth,NULL);//阻塞
	pthread_join(wth,NULL);
	pthread_cond_destroy(&s.rc);//销毁
	pthread_cond_destroy(&s.wc);
	pthread_mutex_destroy(&s.rm);
	pthread_mutex_destroy(&s.wm);
	return 0;
}

线程信号量

信号量的一些操作:c先运行,c运行后释放sem2,让b运行,b运行释放sem1,让a运行

#include<stdio.h>
#include<semaphore.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<time.h>
//定义线程信号量
sem_t sem1;
sem_t sem2;

void* a_fn(void* arg)//线程执行函数
{
	sem_wait(&sem1);//调用阻塞,等待释放
	puts("thread_a sem1 running");
	return (void*)0;
}
void* b_fn(void* arg)
{
	sem_wait(&sem2);//同样调用阻塞,等待阻塞
	puts("thread_b sem2 running");
	sem_post(&sem1);//sem1++;让sem1=0继续运行
	return (void*)0;
}
void* c_fn(void* arg)
{
	puts("thread_c running");
        sem_post(&sem2);//释放sem2信号量,sem2++,让b线程运行
	return (void*)0;
}
int main()
{
	pthread_t a,b,c;
	sem_init(&sem1,0,0);//信号量初始化
	sem_init(&sem2,0,0);

	pthread_create(&a,NULL,a_fn,(void*)0);//启动线程
	pthread_create(&b,NULL,b_fn,(void*)0);
	pthread_create(&c,NULL,c_fn,(void*)0);


	pthread_join(a,NULL);//阻塞
	pthread_join(b,NULL);
        pthread_join(c,NULL);

	sem_destroy(&sem1);//销毁
	sem_destroy(&sem2);
	return 0;
}

信号量初值为1,一些合理pv操作可使信号量实现互斥功能

信号量初值为0,一些合理pv操作可使信号量实现同步功能

死锁

死锁主要是两个线程试图同时占用两个资源,并按不同次序锁定相应共享资源

解决:按相同次序锁定相应共享资源;用pthread_mutex_trylock()加锁,是pthread_mutex_lock的非阻塞版

线程信号

sigset_t set;//信号屏蔽
sigemptyset(&set);//清空
sigaddset(&set,SIGALRM);
pthread_sigmask(SIG_SETMASK,&set,NULL);//对主控线程屏蔽SIGALRM信号
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include<time.h>
void sig_handler(int sign)
{
	printf("pthread signal id %lx\n",pthread_self());
	if(sign==SIGALRM)
		puts("timeout...");
	alarm(3);
}
void* th_fn(void* arg)
{
	if(signal(SIGALRM,sig_handler)==SIG_ERR)
	{
		perror("signal sigalrm error");
	}
	//在子线程中设置定时器
	alarm(3);
	int i=1;
	for(;i<=30;i++)
	{
		printf("pthread %lx i:%d\n",pthread_self(),i);
		sleep(1);
	}
	return (void*)0;
}
void* th_fn2(void* arg)//子线程2运行函数
{
	pthread_t th1=(pthread_t)arg;//强转,获得子线程1
	int i;
	for(i=1;i<=10;i++)
	{
		if(i==5)
		{
			pthread_cancel(th1);//在子线程2中通过子线程1,终止子线程1运行
			alarm(0);//取消定时器
		}
		printf("pthread2 cancel:%lx i:%d\n",pthread_self(),i);
		sleep(1);
	}
}
int main()
{
	pthread_t th,th2;
	pthread_attr_t attr;//定义线程属性
	pthread_attr_init(&attr);//初始化线程属性
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//以分离状态启动线程
	int err;
	if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)
		perror("thread create error");
	
	if((err=pthread_create(&th2,&attr,th_fn2,(void*)th))!=0)//用子线程2终止子线程1,传入的是子线程1的数据th
		perror("thread create error");
	
	sigset_t set;//信号屏蔽
	sigemptyset(&set);//清空
	sigaddset(&set,SIGALRM);
	pthread_sigmask(SIG_SETMASK,&set,NULL);//对主控线程屏蔽SIGALRM信号

	while(1)
	{
		printf("control pthread %lx running..\n",pthread_self());
		sleep(10);
	}
	puts("control pthread over");
	return 0;
}

gcc编译

链接头文件的编译