系统编程——多线程和互斥锁

一、线程的概念

windows系统和linux系统都是多任务的操作系统,任务(task)就是系统调度器的调度实体,而任务是存在在进程中的,只不过有些进程中只有一个任务,有些进程中则可能会存在多个任务。

在这里插入图片描述

操作系统是以进程为单位分配资源的,如果一个进程中只有一个任务,则该任务拥有进程的全部资源,如果进程中有多个任务,则这些任务共享进程的资源。为了可以让进程中的多个任务得到运行,可以把任务放在线程(thread)中,然后由调度器进行调度,而线程是系统调度资源的最小单位。

在这里插入图片描述

线程和进程之间的关系,类似于工厂和工人之间的关系,进程好比是工厂,线程就如同工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们相互配合,共同保证整个工厂的平稳运行。

每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间、文件资源等。一个进程至少包含 1个主线程,可以包含n个子线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。

在这里插入图片描述

注意:由于系统分配资源是以进程为单位的,而进程至少会执行一个任务,如果进程没有创建其他线程,则程序中的main函数就是一条线程,也被称为主线程。

二、线程的创建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:线程使用的是进程的资源,所以进程退出之后线程也会退出,而进程结束的条件之一就是程序的main函数退出,所以也就是当程序中的main函数调用return语句则进程会被终止,所以进程的其他的线程也会一起退出。

三、线程的结束

Linux系统提供多种方式让线程终止,其中比较常用的方式是调用pthread_exit()函数,利用该接口可以实现让线程终止的目的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:主线程如果调用该函数也可以被终止,但是不会影响进程中其他线程的运行,但是如果主线程中调用exit()或者return语句,就会导致进程结束,则所有的线程就会终止。

四、线程的接合

与进程类似,线程退出之后不会立即释放其所占有的系统资源,而会成为一个僵尸线程。为了防止线程退出之后还占用进程资源,所以Linux系统中提供了一个名称叫做pthread_join()的函数接口,其他线程可使用 pthread_join() 来释放僵尸线程的资源,并可获得其退出时返回的退出值,该函数被称为线程的接合函数。

在这里插入图片描述

五、线程的属性

在这里插入图片描述
一般情况下线程的属性都是在线程创建的过程中进行设置,Linux系统提供了专门的结构来存储线程的属性,只不过必须先调用pthread_attr_init()函数对线程属性进行初始化,函数使用规则如下图:
在这里插入图片描述
在众多的线程属性中,有一个可以让线程在终止之后自动释放资源的属性,那就是线程的分离属性。

当线程终止时如果不需要把退出值传递给其他线程,则可以把线程设置为分离属性,设置为分离属性下的线程在退出之后,会自动释放其占用的系统资源。

Linux系统提供了一个名称叫做pthread_attr_setdetachstate()的函数,用户利用该函数可以在设置线程的属性为分离属性,注意:设置为分离属性的线程是无法被接合的。

在这里插入图片描述
可以在线程的任务函数中强制把自身的属性设置为分离属性,Linux系统中提供了一个名称叫做pthread_detach()的函数,可以强制分离。
在这里插入图片描述
Linux系统提供了一个名称叫做pthread_self()的函数接口,线程的TID仅限于进程内部的线程间有效。当需要要对某条线程执行诸如发送信号、取消、阻塞接合等操作时,都需要用到线程的ID。
在这里插入图片描述
通过之前的学习,可以知道一个进程中可以存在多条线程,每条线程可以执行一个任务,而线程是并发执行的,这样可以提高程序的运行效率,但是系统分配资源是以进程为单位的,而进程中的所有线程会共享这些资源。

六、互斥锁

可以利用线程间的同步和互斥机制,来达到线程对资源的有序访问,同步指的是控制两个进度使之有先有后,次序可控,而互斥指的是控制两个进度使之互相排斥,不同时运行。
在这里插入图片描述

为了实现线程对资源的合理访问,并且在访问的过程中不会出现线程竞争的情况,Linux系统就提供了一种实现互斥机制的方案,就是互斥锁。

在这里插入图片描述
互斥锁的本质是一个二值信号量,任何一条线程要开始运行互斥区间的代码,都必须先获取互斥锁,如果其中一条线程抢先获取了互斥锁之后,其余线程就无法再次获取了,相当于给相关的资源加了把锁,直到使用者主动解锁,其余线程方可有机会获取这把锁。

(1)初始化互斥锁

可以看到,Linux系统中提供了一个叫做pthread_mutex_init函数接口,用户利用该函数就可以对互斥量进行初始化,注意:没有经过初始化的互斥量是不能直接使用的!

在这里插入图片描述
互斥量的本质就是一个变量,变量的类型是pthread_mutex_t,要注意对该变量的赋值,需要赋值为PTHREAD_MUTEX_INITIALIZER,再去调用pthread_mutex_init()即可。
在这里插入图片描述

当然,除了调用函数接口实现互斥锁的初始化之外,用户也可以直接定义一个全局变量,这样其他线程都可以使用互斥锁,但是记得对变量进行初始化为PTHREAD_MUTEX_INITIALIZER。

(2)上锁解锁操作
在这里插入图片描述
注意:不要在同一个线程中对已经上锁的互斥量重新上锁,这样会导致死锁出现,应该避免该情况出现,上锁之后应该在使用完资源后及时进行解锁。

提示:如果多条线程访问临界资源时,可以使用互斥锁,注意线程应该在访问资源前上锁,访问完成后进行解锁。

七、练习

1.

编写一个程序,主线程中创建一个子线程,容纳后让主线程先退出并返回一个值,子线程接合主线程后输出主线程的退出值,然后子线程退出。提示:获取线程自身的ID可以调用pthread_self()函数。

/*
 * Copyright (c)
 * 
 * date: 2025-8-11
 * 
 * author: Charles
 * 
 * function name : pthread_self_test.c
 *
 * function: 编写一个程序,主线程中创建一个子线程,容纳后让主线程先退出并返回一个值,
 * 子线程接合主线程后输出主线程的退出值,然后子线程退出。提示:获取线程自身的ID可以调用pthread_self()函数。
 *
 */
#include <stdio.h>
#include <pthread.h>

pthread_t pthread_main;

void* task(void*arg)
{
    int* state;
    pthread_join(pthread_main, (void**)&state);
    printf("Main thread exit state: %d\n", *state);
    
    pthread_exit(NULL); // 子线程退出
}

int main()
{
    pthread_t pthread_child;
    pthread_create(&pthread_child, NULL, task, NULL);

    int state = 10;
    pthread_main = pthread_self();
    pthread_exit(&state);


    return 0;
}

2.

设计一个程序,要求在程序中创建两条子线程,主线程创建一个线程的属性对象,并对线程的属性对象进行初始化之后,要求创建的子线程的属性设置为分离属性,子线程A输出”hello”,子线程B输出”world”,主线程创建子线程之后就终止。

/*
 * Copyright (c)
 * 
 * date: 2025-8-2
 * 
 * author: Charles
 * 
 * function name : pthread_detach_test.c
 *
 * function: 设计一个程序,要求在程序中创建两条子线程,主线程创建一个线程的属性对象,
 * 并对线程的属性对象进行初始化之后,要求创建的子线程的属性设置为分离属性,
 * 子线程A输出”hello”,子线程B输出”world”,主线程创建子线程之后就终止。  
 *
 */

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

void* taskA(void* arg){
	pthread_detach(pthread_self());
	while(1){

		printf("hello \n");
		pthread_exit(NULL);
		sleep(1);
	}
}

void* taskB(void* arg){
	pthread_detach(pthread_self());
	while(1){

		printf("world \n");
		pthread_exit(NULL);
		sleep(1);
	}
}

int main(int argc, char const *argv[])
{
	pthread_t thread1;
	pthread_t thread2;

	// pthread_attr_t thread_addr;

	// pthread_attr_init(&thread_addr);

	// pthread_attr_setdetachstate(&thread_addr, PTHREAD_CREATE_DETACHED);



	pthread_create(&thread1, NULL, taskA, NULL);
	pthread_create(&thread2, NULL, taskB, NULL);

	pthread_exit(NULL);

	return 0;
}

3.

创建一个进程,在主线程中创建一个子线程,主线程和子线程都会使用相同的临界资源(全局变量),要求线程之前使用互斥锁实现资源的互斥访问。

/*
 * Copyright (c)
 * 
 * date: 2025-8-2
 * 
 * author: Charles
 * 
 * function name : pthread_mutex_test.c
 *
 * function: 创建一个进程,在主线程中创建一个子线程,主线程和子线程都会使用相同的临界资源(全局变量),
 * 要求线程之前使用互斥锁实现资源的互斥访问。  
 *
 */

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

volatile int data = 6;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

void* task(void* arg){
	while(1){

		pthread_mutex_lock(&mtx);
		data += 10;
		printf("task : %d\n", data);
		pthread_mutex_unlock(&mtx);
		sleep(1);
	}
	

}

int main(int argc, char const *argv[])
{
	pthread_t thread;

	pthread_create(&thread, NULL, task, NULL);

	while(1){

		pthread_mutex_lock(&mtx);
		data += 10;
		printf("main : %d\n", data);
		pthread_mutex_unlock(&mtx);
		sleep(1);
	}

	return 0;
}

在这里插入图片描述
希望各位靓仔靓女点赞,收藏,关注多多支持,我们共同进步,后续我会更新更多的面试真题,你们的支持将是我前进最大的动力
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值