7 线程

1、基本概念

1.1 线程的基本概念

  • 线程就是程序的执行路线,即进程内部的控制序列,或者说是进程的子任务
  • 一个进程可以同时拥有多个线程,即同时被系统调度的多条执行路线,但至少要有一个主线程
  • 一个进程的所有线程都共享进程的代码区、数据区、BSS区、堆区、命令行参数和环境变量,唯有栈区是一个线程一个。
  • 一个进程的所有线程共享系统内核中与进程有关的部分资源,如文件描述符表、信号处理函数、工作目录、各种ID等。
  • 一个进程的每个线程都有一个独立的D、独立的寄存器上下文、独立的调度策略和优先级、独立的信号掩码、独立的errno以及线程私有数据。

1.2 线程调度

  • 系统内核中专门负责线程调度的处理单元被称为调度器。调度器将所有处于就绪状态(没有阻塞于任何系统调用上)的线程排成一个队列,即所谓就绪队列
  • 调度器从就绪队列中获取队首线程,为其分配一个时间片,并令处理机执行该线程,过了一段时间:
    • 该线程的时间片耗尽,调度器立即中止该线程,并将其排到就绪队列的尾端,接着从队首获取下一个线程
    • 该线程的时间片未耗尽,但需阻塞于某系统调用,比如等待/O或者睡眠。调度器会中止该线程,将其从就绪队列移至等待队列,直到其等待的条件满足后,在被移回就绪都队列
    • 在低优先级线程执行期间,有高优级线程就绪,后者会抢占前者的时间片
  • 若就绪队列为空,侧系统内核进入空闲状态,直至其非空
    总结:
  • 进程是资源分配的最小单位
  • 线程是系统调度的最小单位

2、POSIX线程

  • 相关函数
    1:pthread_create 创建新线程
// 头文件 pthread.h
int pthread_create(pthread_t* tid,pthread_attr_t const* attr,void*(* start_routine)(void*),void* arg)
- 功能:创建新线程
- 参数:
	- tid:输出线程ID。pthread_tunsigned long int
	- attr:线程属性,NULL表示缺省属性。
	- start_routine:线程过程函数指针,所指向的函数将在被创建的线程中执行。
	- arg:传递给线程过程函数的参数。
- 返回值:成功返回O,失败返回错误码!

说明:
① pthread_create函数本身并不调用线程过程函数,而是在系统内核中开启独立的线程,并立即返回,在该线程中执行线程过程函数。
② pthread_create函数返回时,所指定的线程过裎函数可能尚未执行,也可能正在执行,甚至可能已经执行完毕。
③ 主线程和通过pthread_create函数创建的多个子线程,在时间上"同时"运行,如果不附加任何同步条件,则它们每一个执行步骤的先后顺序是完全无法确定的,这就叫做自由并发。
2:thread_proc 线程过程函数

void* thread_proc(void* arg){...}
- 定义线程过程函数,该函数会在所创建的线程中被执行,代表了线程的任务。
- 而main函数其实就是主线程的线程过程函欲。
- main函数一旦返回,主线程即告结束、主线程一旦结束,进程即告结束。进程一旦结束,其所有的子线程统统结束
  • 案例
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

// 线程过程函数
void* pthread_fun(void* arg){
    printf("%lu线程:我是子线程,内容为%s\n",pthread_self(),(char*)arg);
    return NULL;
}

int main(void){
	printf("%lu线程:我是主线程\n",pthread_self());
	pthread_t tid;// 用来输出子线程的ID
	int error = pthread_create(&tid,NULL,pthread_fun,"hello");
	if(error){
		fprintf(stderr," pthread_create:%s\n",strerror(error));
		return -1;
	}
	sleep(1);// 延时
	return 0;
}

3、汇合线程

1:pthread_join 等待子线程终止

// 头文件 pthread.h
int pthread_join(pthread_t tid,void** retval);
- 功能:等待子线程终止,即其线程过程函数返回,并与之会合,同时回收该线程的资源
- 参数:
	- tid:线程ID。
	- retval:输出线程过程函数的返回值
- 返回值:成功返回0,失败返回错误码。

在父线程中调用pthread_join函数等待子线程的终止,并回收其资源。如果调用该函数时子线程已经终止,该函数会立即返回,如果调用该函数时子线程尚在运行中,该函数会阻塞,直至子线程终止。该函数在返回成功的同时通过其retval参数向调用者输出子线程线程过程函数的返回值。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define PI 3.14

// 线程过程函数
void* area(void* arg){
	double* r = (double*)arg;
	double *p = malloc(sizeof(double));
	*p = PI*(*r)*(*r);
	printf("圆的面积是:%lg\n",*p);
	return p;
}

int main(){
	double r =10;
	// 创建子线程,计算圆的面积
	pthread_t tid;
	int errno = pthread_create(&tid,NULL,area,&r);
	if(errno){
		fprintf(stderr,"pthread_create:%s",strerror(errno));
		return -1;
	}
	double *p; 
	errno = pthread_join(tid,(void**)&p);
	if(errno){
		fprintf(stderr,"pthread_join:%s",strerror(errno));
	}
	printf("圆的面积是:%lg\n",*p);
	free(p);
	return 0;
}

4、分离线程

  • 一个线程在默认情况下都是可汇合线程,这样的线程在终止以后其资源会被保留,其中含有线程过程函数的返回值及线程ID等信息,父线程可以通过pthread_join函数获得线程过程函数的返回值,同时释放这些资源。
  • 如栗在创建线程以后,对其调用pthread_detach函数并返回成功,该线程即成为分离线程,这样的线程终止以后其资源会被系统自动回收,不需要也无法通过pthread_join函数汇合它。
// 头文件 pthread.h
int pthread_detach(pthread_t tid);
- 功能:使指定的线程进入分离状态
- 参数:
	- tid线程的D
- 返回值:成功返回0,失败返回错误码。

案例

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
// 线程过程函数
void* pthread_fun(void* arg){
	for(int i=0;i<100;i++){
		putchar('-');
		usleep(50000);
	}
	return NULL;
}

int main(){
	setbuf(stdout,NULL);
	pthread_t tid;
	pthread_create(&tid,NULL,pthread_fun,NULL);
	pthread_detach(tid);
	for(int i=0;i<100;i++){
		putchar('+');
		usleep(100000);
	}
	return 0;
}

5、线程ID

pthread_t类型的线程ID是POSIX线程库内部维护的线程的唯一标识,通常表现为一个很大的整数,跨平台,可移植。

// 头文件 pthread.h
pthread_t pthread_self(void); //返回调用线程的(POSIX线程库的)TID。

syscall(SYS_gettid)函数返回是一个long int类型整数,是系统内核产生线程唯一标识。一个进程的PID其实就是它的主线程的TID。

// 头文件 sys/syscall.h
long int syscall(SYS_gettid);//返回调用线程的(系统内核的)TID

案例

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
// 线程过程函数
void* pthread_fun(void* arg){
	printf("子线程:进程PID %d\n",getpid());
	printf("子线程:POSIX TID %lu\n",pthread_self());
	printf("子线程:内核 TID %lu\n",syscall(SYS_gettid));
	return NULL;
}
int main(){
	printf("主线程:进程PID %d\n",getpid());
	printf("主线程:POSIX TID %lu\n",pthread_self());
	printf("主线程:内核 TID %lu\n",syscall(SYS_gettid));
	pthread_t tid;
	pthread_create(&tid,NULL,pthread_fun,NULL);
	pthread_join(tid,NULL);
	return 0;
}

在这里插入图片描述

pthread_t类型在不同系统会被实现为不同的数据类型,甚至可能会使用结构体。因此判断两个线程ID是否相等或不相等,最好不要使用"==“或”!="运算符,因为这些关系运算符只能用于C语言内置的简单类型,而对于结构类型的数据不适用。

// 头文件 pthread.h
int pthread_equal(pthread_t t1,pthread_t t2);// 若两个参数所表示的TID相等返回非零,否则返回0。

6、并发冲突

  • 案例
#include <stdio.h>
#include <pthread.h>

int g_cn = 0;
void* pthread_fun(void* arg){
	for(int i=0;i<1000;i++){
		g_cn++;
	}
	return NULL;
}

int main(){
	pthread_t t1,t2;
	pthread_create(&t1,NULL,pthread_fun,NULL);
	pthread_create(&t2,NULL,pthread_fun,NULL);
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	printf("%d\n",g_cn);
	return 0;
}
  • 并发冲突问题
    任何时候只允许一个线程持有共享数据,其它线程必须阻塞于调度队列之外,直到数据持有者不再持有该数据为止
  • 资源竞争问题
    任何时候只允许部分线程拥有有限的资源,其它线程必须阻塞于调度队列之外,直到资源拥有者主动释放其所拥有的资源为止
  • 条件等待问题
    当某些条件一时无法满足时,一些线程必须阻塞于调度队列之外,直到令该条件满足的线程用信号唤醒它们为止
  • 线程同步
    某些情况下,需要在线程之间建立起某种停等机制,即一或多个线程有时必须停下来,等待另外一或多个线程执行完一个特定的步骤以后才能继续执行,这就叫同步

7、互斥锁

  • 线程间可以通过互斥锁解决资源竞争的问题。
  • 任何时候都只能一个线程持有互斥锁,即加锁成功,在其持有该互斥锁的过程中,其它线程对该锁的加锁动作都会引发阻塞,只有当持有互斥锁的线程主动解锁,那些在加锁动作上阻塞的线程中的一个采用恢复运行并加锁成功。
  • pthread_mutex_t表示互斥锁数据类型
    相关函数
    1:pthread_mutex_init 初始化互斥体
// 头文件 pthread.h
int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t const* attr);
- 功能:初始化互斥体
- 参数:
	- mutex:互斥体
	- attr:互斥体属性
- 返回值:成功返回0,失败返回错误码。
- 也可以静态方式初始化互斥锁:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2:pthread_mutex_destroy 销毁互斥体

// 头文件 pthread.h
int pthread_mutex_destroy(pthread_mutex_t* mutex);
- 功能:销毁互斥体
- 参数:
	- mutex:互斥体
- 返回值:成功返回0,失败返回错误码

3:pthread_mutex_lock 锁定互斥体

// 头文件 pthread.h
int pthread_mutex_lock (pthread_mutex_t* mutex);
- 功能:锁定互斥体
- 参数:mutex互斥体
- 成功:返回0,失败返回错误码

4:pthread_mutex_unlock 解锁互斥锁

// 头文件 pthread.h
int pthread_mutex_unlock(pthread_mutex_t* mutex);
- 功能:解锁互斥锁
- 参数:mutex互斥锁
- 成功:返回0,失败返回错误码

案例

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

int g_cn = 0;
// 互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* pthread_fun(void* arg){
	pthread_mutex_lock(&mutex);
	for(int i=0;i<100000;i++){
			g_cn++;
	}
	pthread_mutex_unlock(&mutex);
	return NULL;
}

int main(){
	pthread_t t1,t2;
	pthread_create(&t1,NULL,pthread_fun,NULL);
	pthread_create(&t2,NULL,pthread_fun,NULL);
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	printf("%d\n",g_cn);
	pthread_mutex_destroy(&mutex);
	return 0;
}

8、条件变量

  • 一个线程在某种条件不满足的情况下,无法进行后续工作,这时它就可以睡入某个条件变量,这时会有其它线程为其创建条件,一旦条件满足可以唤醒,那些在相应条件变量中睡眠的线程继续运行。
  • 通过pthread_cond_t类型来表示条件变量
    相关函数
    1:pthread_cond_init 初始化条件变量
// 头文件 pthread.h
int pthread_cond_init(pthread_cond_t* cond,const pthread_condattr_t* attr);
- 功能:初始化条件变量
- 参数:
	- cond条件变量
	- attr条件变量属性
- 返回值:成功返回0,失败返回错误码
- 也可以静态方式初始化条件变量:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2:pthread_cond_destroy 销毁条件变量

// 头文件 pthread.h
int pthread_cond_destroy(pthread_cond_t* cond)
- 功能:销毁条件变量
- 参数:cond条件变量
- 返回值:成功返回0,失败返回错误码

3:pthread_cond_wait 睡入条件变量

// 头文件 pthread.h
int pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex);
- 功能:睡入条件变量
- 参数:
	- cond 条件变量
	- mutex 互斥锁
- 返回值:成功返回O,失败返回错误码
// 这是函数会让当前线程睡眠,并解开mutex锁,当条件满足时,线程睡醒,会为当前线程加上mutex锁

4:pthread_cond_signal 唤醒条件变量

// 头文件 pthread.h
int pthread_cond_signal(pthread_cond_t* cond);
- 功能:唤醒在条件变量中睡眠的一个线程
- 参数:cond条件变量
- 返回值:成功返回O,失败返回错误码
  • 一个线程调用pthread_cond_waiti函数进入阻塞状态,直到条件变量cond收到信号为止,阻塞期间互斥锁mutex会被释放。另一个线程通过pthread_cond_signal函数向条件变量cond发送信号,唤醒在其中睡眠的一个线程,该线程即从pthread_cond_wait函数中返回,同时重新获得互斥锁mutex。
    案例
//通过条件变量解决生产者和消费者问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
char g_storage[10];// 仓库
int g_stock=0;// 库存量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
pthread_cond_t pcond = PTHREAD_COND_INITIALIZER;// 生产者条件变量
pthread_cond_t ccond = PTHREAD_COND_INITIALIZER;// 消费者条件变量
// 显示生产和消费过程
void show(char* who,char* op,char prod){
	printf("%s:",who);
	for(int i=0;i<g_stock;i++){
		printf("%c",g_storage[i]);
	}
	printf("%s%c\n",op,prod);
}
// 生产这线程
void* producer(void* arg){
	char* who = (char*)arg;
	for(;;){
		// 加锁
		pthread_mutex_lock(&mutex);
		// 生产
		// 判满
		if(g_stock == sizeof(g_storage)){
			printf("%s:满仓\n",who);
			// 睡入
			pthread_cond_wait(&pcond,&mutex);
		}
		char prod='A'+rand()%26;
		show(who,"<---",prod);
		g_storage[g_stock++]=prod;
		// 唤费者
		pthread_cond_signal(&ccond);
		// 解锁
		pthread_mutex_unlock(&mutex);
		usleep((rand()%100)*1000);
	}
	return NULL;
}
// 消费者线程
void* consumer(void* arg){
	char* who = (char*)arg;
	for(;;){
		// 加锁
		pthread_mutex_lock(&mutex);
		// 消费
		// 判空
		if(g_stock ==0){
			printf("%s:空仓\n",who);
			pthread_cond_wait(&ccond,&mutex);
		}
		char prod = g_storage[--g_stock];
		show(who,"--->",prod);
		// 唤醒生产者
		pthread_cond_signal(&pcond);
		// 解锁
		pthread_mutex_unlock(&mutex);
		usleep((rand()%100)*1000);
	}
	return NULL;
}
int main(){
	srand(getpid());// 随机数种种子
	pthread_t t1,t2;
	pthread_create(&t1,NULL,producer,"生产者");
	pthread_create(&t2,NULL,consumer,"消费者");
	getchar();
	return 0;
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 题目需要使用同步方法实现打印递增的数字。有3个线程,分别对应线程1、线程2、线程3,线程1先打印1、2、3、4、5;线程2接着打印6、7、8、9、10;线程3最后打印11、12、13、14、15。接下来再由线程1打印16、17、18、19、20。根据题目描述,程序的输出结果应该是:线程1:1 2 3 4 5 线程2:6 7 8 9 10 线程3:11 12 13 14 15 线程1:16 17 18 19 20。 ### 回答2: 要实现这个程序,需要用到同步方法,确保三个线程能够有序地执行任务。具体实现方法如下: 首先创建一个类NumPrinter,继承Thread类,并添加一个成员变量num,记录当前要打印的数字。同时添加一个静态对象current,用于记录当前是哪个线程在执行打印任务。 然后重写run方法,用while循环控制每个线程的打印范围,并使用synchronized关键字修饰run方法,确保同一时刻只有一个线程执行任务。 在run方法中,先判断当前是哪个线程在执行任务,然后使用if语句判断当前打印的数字是否在该线程的任务范围内。若是,则打印出该数字,并将num加1,然后使用wait方法暂停当前线程。如果不在任务范围内,则不进行打印,而是使用wait方法暂停当前线程,等待其他线程打印完毕。 此时,另外两个线程就可以被唤醒,开始执行打印任务,直到再次轮到该线程打印数字。 最后,在main方法中创建三个NumPrinter的实例,分别启动三个线程,并使用join方法等待它们执行完毕,保证输出的数量正确。 详细的代码实现见下: ### 回答3: 本题需要使用多线程编程中锁机制的同步方法来实现。具体步骤如下: 1. 定义一个全局变量num并初始化为1,表示输出的起始数字为1。 2. 定义一个锁对象lock来控制三个线程的同步输出。 3. 定义三个线程分别打印从num开始递增5的数字,每个线程打印完之后释放锁,让下一个线程来占用锁并输出相应的数字。 4. 在每个线程中使用while循环判断num的值是否小于等于75,如果小于则输出相应的数字和线程名,并将num加5,否则退出循环并结束线程。 5. 最后在主线程中使用join方法等待三个子线程运行结束。 代码实现如下: ``` import threading # 定义全局变量和锁对象 num = 1 lock = threading.Lock() # 定义线程1打印1-5的函数 def thread1(): global num while num <= 75: lock.acquire() for i in range(5): if num <= 75: print("线程1:" + str(num)) num += 1 lock.release() # 定义线程2打印6-10的函数 def thread2(): global num while num <= 75: lock.acquire() for i in range(5): if num <= 75: print("线程2:" + str(num)) num += 1 lock.release() # 定义线程3打印11-15的函数 def thread3(): global num while num <= 75: lock.acquire() for i in range(5): if num <= 75: print("线程3:" + str(num)) num += 1 lock.release() # 创建三个子线程并启动 t1 = threading.Thread(target=thread1) t1.start() t2 = threading.Thread(target=thread2) t2.start() t3 = threading.Thread(target=thread3) t3.start() # 等待三个子线程运行结束 t1.join() t2.join() t3.join() ``` 运行结果如下: ``` 线程1:1 线程1:2 线程1:3 线程1:4 线程1:5 线程2:6 线程2:7 线程2:8 线程2:9 线程2:10 线程3:11 线程3:12 线程3:13 线程3:14 线程3:15 线程1:16 线程1:17 线程1:18 线程1:19 线程1:20 线程2:21 线程2:22 线程2:23 线程2:24 线程2:25 线程3:26 线程3:27 线程3:28 线程3:29 线程3:30 线程1:31 线程1:32 线程1:33 线程1:34 线程1:35 线程2:36 线程2:37 线程2:38 线程2:39 线程2:40 线程3:41 线程3:42 线程3:43 线程3:44 线程3:45 线程1:46 线程1:47 线程1:48 线程1:49 线程1:50 线程2:51 线程2:52 线程2:53 线程2:54 线程2:55 线程3:56 线程3:57 线程3:58 线程3:59 线程3:60 线程1:61 线程1:62 线程1:63 线程1:64 线程1:65 线程2:66 线程2:67 线程2:68 线程2:69 线程2:70 线程3:71 线程3:72 线程3:73 线程3:74 线程3:75 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

启航zpyl

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

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

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

打赏作者

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

抵扣说明:

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

余额充值