线程知识点详细梳理

线程

线程的概念

  1. 每个用户进程有自己的虚拟地址空间。
  2. 系统为每个用户进程创建一个task_struct 来描述该进程 struct task_struct。
  3. task_struct 和地址空间映射表一起用来表示一个进程。
  4. 由于进程的虚拟地址空间是私有的,因此进程切换开销很大。
  5. 为了提高系统的性能,linux引入轻量级进程, 起名为线程。
  6. 在同一个进程中创建的线程共享该进程的地址空间。
  7. Linux里同样用task_struct来描述一个线程

总结:

  1. 通常线程指的是共享相同虚拟地址空间的多个任务。
  2. 使用多线程, 大大提高了任务切换的效率。

注意:

1.每个进程中至少有一个线程,就是主线程,还可以产生多个线程共享4G内存空间,线程切换只需要虚拟CPU(寄存器)。
2.同样用task_struct来描述一个线程,线程和进程都参于统一的调度。
3.进程代表资源分配的最小单位,线程是最小的调度单位。

线程的相关函数

1.线程的创建

头文件:#include <pthread.h>

定义一个:pthread_t tid; //线程号

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建线程

参数:
thread: &tid 线程号的地址
attr: NULL 线程函数默认的属性
start_routine: 线程函数的函数名
arg: 给线程函数传递的参数 NULL

返回值:
成功: 0
失败: -1

gcc编译时,记得在末尾加上 -pthread。

void *fun1(void *arg)
{
	while(1)
	{
		printf("fun1 is running!\n");
		sleep(2);
	}
}
void *fun2(void *arg)
{
	while(1)
	{
		printf("fun2 is running!\n");
		sleep(2);
	}
}
int main(int argc, const char *argv[])
{
	pthread_t tid1,tid2; //线程号
	int ret = pthread_create(&tid1, NULL, fun1, NULL);
	if(ret == -1){
		perror("pthread_create");
		return -1;
	}
	ret = pthread_create(&tid2, NULL, fun2, NULL);
	if(ret == -1){
		perror("pthread_create");
		return -1;
	}
	while(1)
	{
		printf("main is running!\n");
		sleep(2);
	}
	return 0;
}

结果会每隔1秒交替打印:
main is running!
fun1 is running!
fun2 is running!

2.回收线程资源

函数原型:int pthread_join(pthread_t thread, void **retval);
功能:主线程等待子线程结束,然后回收资源
重点:阻塞
参数:
thread: tid 线程号
retval: NULL (接收子线程结束的返回值)
返回值:
成功:0
失败:-1
我们来看下它是如何阻塞的:

void *fun(void *arg)
{
    int n = 3;
    while(n--)
    {
        printf("fun is running\n");
        sleep(1);
    }
}
int main(int argc, char *argv[])
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,fun,NULL);
    if (-1 == ret)
    {
        perror("pthread_create");
        return -1;
    }
    printf("text1---------\n");
    pthread_join(tid,NULL);
    printf("text2---------\n");
    while(1)
    {
        printf("main is running\n");
        sleep (1);
    }
    return 0;
}

结果为:

text1---------
fun is running
fun is running
fun is running
text2---------
main is running
。。。。。。。

text1打印出来后阻塞着,仅子线程运行。阻塞3秒以后text2打印出来,进入主线程无限循环。

函数原型:int pthread_detach(pthread_t thread);
功能:主线程和子线程分离开来,系统自动回收资源
重点:非阻塞
参数:
thread: tid 线程号

返回值:
成功:0
失败: -1
我们来看下这个非阻塞函数:

void *fun(void *arg)
{
    int n = 3;
    while(n--)
    {
        printf("fun is running\n");
        sleep(1);
    }
}
int main(int argc, char *argv[])
{
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,fun,NULL);
    if (-1 == ret)
    {
        perror("pthread_create");
        return -1;
    }
    printf("text1---------\n");
    pthread_detach(tid);
    printf("text2---------\n");
    while(1)
    {
        printf("main is running\n");
        sleep (1);
    }
    return 0;
}

结果为:

text1---------
text2---------
main is running
fun is running
main is running
fun is running
main is running
fun is running
main is running
main is running

text1和text2都打印出来,一起运行3秒以后,系统自动回收子线程资源,进入主线程无限循环。

3.结束子线程

函数原型:void pthread_exit(void *retval);
功能:结束子线程
参数:
retval : void *
返回值:
成功:
失败:

void *fun(void *arg)
{
    int n = *(int *)arg;//强转下类型接收传递的参数
    printf("n=%d\n",n);
    pthread_exit(arg);//结束后将参数返回
}
int main(int argc, char *argv[])
{
    int m = 66; 
    pthread_t tid;
    int ret = pthread_create(&tid,NULL,fun,(void *)&m);//将参数传给子线程fun
    if (-1 == ret)
    {   
        perror("pthread_create");
        return -1; 
    }   
    void *a;//定义一个指针来接收exit的返回值
    pthread_join(tid,&a);//该函数的第二个参数就是接收exit的返回值
    printf("a= %d\n",*(int *)a);//打印接收的返回值
    sleep (1);
    return 0;
} 

结果为:

n=66
a=66

线程的同步和互斥

线程的有点和缺点:
优点:线程间很容易进行通信,通过全局变量实现数据共享和交换。

缺点:多个线程同时访问共享对象时产生竟态,需要引入同步和互斥机制。

同步和互斥 :为了保护共享资源,避免竟态。

同步:多个任务按理想的顺序/步调来进行访问。
互斥:使多个任务不能同时访问。

1.线程的同步—信号量函数

头文件:#include <semaphore.h>

定义一个:sem_t sem; //信号量的变量

函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem: &sem
pshared: 0:线程 1:进程
value: 初值 0 or 1
返回值:
成功:0
失败:-1

int sem_wait(sem_t *sem); //如果初值是0,阻塞函数,如果初值是1,非阻塞,运行后将初值减1,变为阻塞状态。

int sem_post(sem_t *sem); //加1函数,使前面阻塞的初值变为1,从而不阻塞。

函数简易模型为:

sem_t sem1,sem2;//定义两个信号量变量
初始化:
sem_init(&sem1,0,0);
sem_init(&sem2,0,1);
初始化时一个初值给1一个初值给0
sem_wait(&sem2);sem2初值为1,非阻塞,运行后sem2值为0,阻塞。
需要先运行的线程
sem_post(&sem1);sem1初值为0,到这里sem1值变为1。
sem_wait(&sem1);sem1值变为1后,非阻塞,运行后sem1值变为0,阻塞。
需要后运行的线程
sem_post(&sem2);sem2运行后值为0,运行到这里时,值变为1,返回上面需要先运行线程,进入循环。

2.线程的互斥------互斥锁

定义一个:pthread_mutex_t mutex; //互斥锁的变量

函数原型:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化互斥锁
参数:
mutex: &mutex
mutexattr: NULL (默认属性)
返回值:
成功:0
失败:-1

int pthread_mutex_lock(pthread_mutex_t *mutex);
//加锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);
//解锁

pthread_t tid1,tid2;
pthread_mutex_t mutex;//互斥锁变量
int i,value1, value2; //全局变量
void *fun1(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		i++;
		value1 = i;
		value2 = i;
		pthread_mutex_unlock(&mutex);
	}
}
void *fun2(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex);
		if(value1 != value2)
		{
			printf("%d %d %d\n", i, value1, value2);
		}
		pthread_mutex_unlock(&mutex);
	}
}
int main(int argc, const char *argv[])
{
	pthread_mutex_init(&mutex, NULL); //初始化互斥锁
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_detach(tid1);
	pthread_create(&tid2, NULL, fun2, NULL);
	pthread_detach(tid2);
	while(1);
	return 0;
}

不加锁时,会进行打印。
加锁后,保证读取时都是相同的数,不会打印。

下一篇为进程间通信的知识点总结。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值