Linux 线程

文章介绍了线程的概念、进程与线程的区别,以及如何使用信号量、互斥锁进行线程同步,如sem_post和sem_wait操作以及pthread_mutex_init、pthread_mutex_lock等。此外,还展示了生产者消费者模型在多线程中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程

  • 线程实质上是进程内一条独立的执行路径,它可以被处理器独立的调度,同时共享进程里的资源。
  • 一个进程至少有一个执行线程,线程是在进程内部运行的,本质上是在进程地址空间内运行的。
  • 进程是资源分配的基本单位,线程是调度的基本单位。
  • 线程几乎不独立拥有资源
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>

void *thread_function(void *arg);

char message[]="Hello World";

int main(){
	int res;
	pthread_t a_thread;
	void *thread_result;
	res=pthread_create(&a_thread,NULL,thread_function,(void*)message);
	if(res!=0){
		perror("Thread creation failed");
		exit(EXIT_FAILURE);
	}
	printf("Writing for thread to finish\n");
	res=pthread_join(a_thread,&thread_result);
	if(res!=0){
		perror("Thread join failed");
		exit(EXIT_FAILURE);
	}
	printf("THread joined,it returned %s\n",(char*)thread_result);
	printf("Message is now %s\n",message);
	exit(EXIT_SUCCESS);

}
void * thread_function(void *arg){
	printf("thread_function is running.Argument was %s\n",(char*)arg);
	sleep(3);
	strcpy(message,"Bye!");
	//这个是等待线程结束的时候的返回值,也就是thread_result的值
	pthread_exit("Thank you for the CPU time");
	
}
[cch@aubin thread]$ gcc thread1.c -pthread
[cch@aubin thread]$ ./a.out
Writing for thread to finish
thread_function is running.Argument was Hello World
THread joined,it returned Thank you for the CPU time
Message is now Bye!
[cch@aubin thread]$ 
  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);第一个参数是线程的类型,第二个参数是线程的属性,第三个是函数指针(线程的入口函数),第四个是给函数指针的参数。该函数的成功会返回一个0,错误会返回一个错误码。

  • int pthread_join(pthread_t thread, void **retval);以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。参数 :thread: 线程标识符,即线程ID,标识唯一线程。retval: 用户定义的指针,用来存储被等待线程的返回值。返回值 : 0代表成功。 失败,返回的则是错误号。

  • void pthread_exit(void *value_ptr);线程内部调用该函数用于退出当前线程,参数value_ptr是用于存储线程的退出值传递出去,也可以设为NULL,调用接下来的pthread_join回收线程就可以传出到参数void **value_ptr中。

进程和线程的区别

  • 进程是资源调度的基本单位,每一个进程独立的拥有资源。而同一个进程中的线程会共享进程中的资源,包括全局的变量。

pthread_join与pthread_detach的区别

  • pthread_create创建线程有两种状态,joinable和unjoinable,也就是可结和与分离的区别,默认是joinable状态。
  • 一个可结合的线程能够被其他线程收回其资源和杀死,在被其他线程回收之前,他的存储器资源是不可释放的,所以以默认的属性创建线程时,创建的线程是可结合的,我们需要对线程退出时用pthread_join对线程资源进行回收,只有当pthread_join函数返回的时候,创建的线程才算终止,才会释放自己占用的系统资源。
  • 一个不可结合的线程,线程结束后是会自动释放占用的资源。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

void* thread_function(void* arg);


int main(){
	int res;
	pthread_t a_thread;
	void *thread_result;
	res=pthread_create(&a_thread,NULL,thread_function,NULL);
	if(res!=0){
		printf("Thread creation failed\n");
		exit(EXIT_FAILURE);

	}
	//res=pthread_detach(a_thread);
	res=pthread_join(a_thread,&thread_result);
	if(res!=0){
		printf("Thread detach failed\n");
		exit(EXIT_FAILURE);

	}
	printf("main finish\n");
	while(1){
		printf("main running...\n");
		sleep(1);
	}
	exit(EXIT_SUCCESS);

}

void* thread_function(void* arg){
	while(1){
		printf("thread running...\n");
		sleep(1);
	}
	exit(EXIT_SUCCESS);

}
# pthread_detach运行结果
[cch@aubin thread]$ gcc thread2.c -pthread
[cch@aubin thread]$ ./a.out
main finish
main running...
thread running...
thread running...
main running...
thread running...
main running...
main running...
thread running...
thread running...
main running...
thread running...
main running...
thread running...
main running...
main running...
thread running...
main running...
thread running...
thread running...
main running...
^C
# pthread_join运行结果
[cch@aubin thread]$ gcc thread2.c -pthread
[cch@aubin thread]$ ./a.out
thread running...
thread running...
thread running...
thread running...
thread running...
thread running...
thread running...
thread running...
thread running...
thread running...
^C
[cch@aubin thread]$ 

控制线程的执行顺序

进程的资源对进程中的线程是共享的,当线程对进程中的资源进行修改时,如果不对线程的执行的顺序加以控制,会导致数据的混乱。

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

void *thread_function(void *arg);
int sign=1;//信号量
char message[]="Hello World";

int main(){
	int res;
	pthread_t a_thread;
	void *thread_result;
	int print_count1=0;//父线程私有

	res=pthread_create(&a_thread,NULL,thread_function,(void*)message);
	if(res!=0){
		perror("Thread creation failed");
		exit(EXIT_FAILURE);
	}

	while(print_count1++<20){
		if(sign==1){
			printf("1 ");
			fflush(stdout);//强制性输出
			sign=2;
		}else sleep(1);
	}
	//printf("Writing for thread to finish\n");
	res=pthread_join(a_thread,&thread_result);
	if(res!=0){
		perror("Thread join failed");
		exit(EXIT_FAILURE);
	}
	//printf("THread joined,it returned %s\n",(char*)thread_result);
	//printf("Message is now %s\n",message);
	exit(EXIT_SUCCESS);

}
void * thread_function(void *arg){
	int print_count2=0;//子线程私有
	while(print_count2++<20){
		if(sign==2){
			fflush(stdout);//强制性输出
			printf("2 ");
			sign=1;
		}else sleep(1);
	}
	sleep(3);
	
	
}
[cch@aubin thread]$ gcc thread1.c -pthread
[cch@aubin thread]$ ./a.out
1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 [cch@aubin thread]$

现象:在执行过程中,上述数据是一次性打印的,不是一个个蹦出来的,这是因为linux是有缓冲的,频繁的输入输出会耗费资源,降低性能,所以linux会等待一批数据,然后一次性输出一批数据。可以使用fflush(stdout);//强制性输出

使用信号量控制线程的并发执行

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

void *thread_function(void *arg);
sem_t bin_sem;
#define WORK_SIZE 1024
char work_area[WORK_SIZE];

int main(){
	int res;
	pthread_t a_thread;
	void *thread_result;
	res=sem_init(&bin_sem,0,0);//信号量初始化为0
	if(res!=0){
		printf("Semaphore initialization failed");
		exit(EXIT_FAILURE);
	}

	res=pthread_create(&a_thread,NULL,thread_function,NULL);
	if(res!=0){
		perror("Thread creation failed");
		exit(EXIT_FAILURE);
	}

	printf("Input some text. Enter 'end' to finish\n");
	while(strncmp("end",work_area,3)!=0){
		fgets(work_area,WORK_SIZE,stdin);
		sem_post(&bin_sem);
	}
	printf("\nWaiting for thread to finish\n");
	res=pthread_join(a_thread,&thread_result);
	if(res!=0){
		perror("Thread join failed");
		exit(EXIT_FAILURE);

	}
	printf("Thread join\n");
	sem_destroy(&bin_sem);//清除信号资源
	exit(EXIT_SUCCESS);

}
void * thread_function(void *arg){
	sem_wait(&bin_sem);
	while(strncmp("end",work_area,3)!=0){
		printf("You input %d characters\n",strlen(work_area)-1);
		sem_wait(&bin_sem);
	}
	
	pthread_exit(NULL);
}
Input some text. Enter 'end' to finish
shhhhiiihwoojaend
You input 17 characters
ajjq
You input 4 characters
end

Waiting for thread to finish
Thread join
[cch@aubin thread]$ 
  • int sem_init(sem_t *sem, int pshared, unsigned int value);sem :指向信号量对象;pshared : 指明信号量的类型。不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。value : 指定信号量值的大小。sem_init() 成功时返回 0;错误时,返回 -1,并把 errno 设置为合适的值。
  • int sem_post(sem_t *sem);sem_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;sem_post() 成功时返回 0;错误时,信号量的值没有更改,-1 被返回,并设置 errno 来指明错误。也即是PV操作中的V操作
  • int sem_wait(sem_t * sem);sem_wait是一个函数,也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果对一个值为0的信号量调用sem_wait(),这个函数就会原地等待直到有其它线程增加了这个值使它不再是0为止。也就是相当于PV操作中的P操作。函数成功返回0,错误的话信号量的值不改动,返回-1.errno设定来标识错误.

互斥锁

#include<pthread.h>  
///< 创建互斥对象,用指定的初始化属性初始化互斥对象  
int pthread_mutex_init(pthread_mutex_t *mutex,   const pthread_mutex_attr_t *mutexattr);  

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

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

///< 加锁,但是如果对象已经上锁则返回EBUSY错误代码而不阻塞  
int pthread_mmutex_trylock(pthread_mutex_t *mutex);  

///< 析构并释放mutex相关资源  
int pthread_mutex_destroy(pthread_mutex_t *mutex);  

互斥锁的使用

pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);  ///< 初始化互斥锁
pthread_mutex_lock(&mutex);       ///< 加锁
///< 操作公共资源
pthread_mutex_unlock(&mutex);     ///< 解锁
pthread_mutex_destroy(&mutex);    ///< 销毁互斥锁

生产者消费者

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

#define QUEUE_SIZE 10 //定义缓冲区的大小

int buffer[QUEUE_SIZE];//缓冲区数组
int in=0,out=0;
sem_t full,empty;
pthread_mutex_t lock;//互斥锁

int produce=0;

//生产者线程函数
void *producer(void* arg){
	while(1){
		produce++;//生产新的产品
		sem_wait(&empty);//V
		pthread_mutex_lock(&lock);//加锁
		buffer[in]=produce;//写入缓冲区
		in=(in+1)%QUEUE_SIZE;//更新输入队列指针
		printf("Produced %d\n",produce);
		pthread_mutex_unlock(&lock);
		sem_post(&full);

		sleep(2);//等待一段时间,生产下一个数据

	}

}

void* consumer(void*arg){
	while(1){
		sem_wait(&full);
		pthread_mutex_lock(&lock);
		int consume=buffer[out];//从缓冲区中取数据
		out=(out+1)%QUEUE_SIZE;
		printf("Comsumed %d\n",consume);
		pthread_mutex_unlock(&lock);
		sem_post(&empty);
		sleep(3);

	}

}

int main(){
	int res;
	sem_init(&empty,0,QUEUE_SIZE);//初始化信号量
	sem_init(&full,0,0);
	pthread_mutex_init(&lock,NULL);//初始化互斥锁

	pthread_t producer_thread,consumer_thread;
	//创建
	printf("starting......\n");
	res=pthread_create(&producer_thread,NULL,&producer,NULL);
	if(res!=0){
		perror("Produce thread creation failed");
		exit(EXIT_FAILURE);
	}
	res=pthread_create(&consumer_thread,NULL,&consumer,NULL);
	if(res!=0){
		perror("Consumer thread creation failed");
		exit(EXIT_FAILURE);
	}
	
	//阻塞
	res=pthread_join(producer_thread,NULL);
	if(res!=0){
		perror("Produce thread join failed");
		exit(EXIT_FAILURE);

	}
	res=pthread_join(consumer_thread,NULL);
	if(res!=0){
		perror("Consumer thread join failed");
		exit(EXIT_FAILURE);

	}
	sem_destroy(&empty);
	sem_destroy(&full);

	pthread_mutex_destroy(&lock);

	exit(EXIT_SUCCESS);

}
[cch@aubin thread]$ ./a.out
starting......
Produced 1
Comsumed 1
Produced 2
Comsumed 2
Produced 3
Comsumed 3
Produced 4
Produced 5
Comsumed 4
Produced 6
Comsumed 5
Produced 7
Produced 8
Comsumed 6
Produced 9
Comsumed 7
Produced 10
Produced 11
Comsumed 8
Produced 12
^C

讨论

阅读以下代码,确定x的值,并在此基础上,说明进程和线程之间的区别

在以上代码中,x的值最后为-1。x的值在主线程中初始化为0,然后在主线程中设为1,在创建的线程fun中设置为-1。
线程和进程的区别在于:线程是调度的基本单位,进程是资源分配的基本单位;进程是程序的执行实例,是独立拥有存储空间的运行单位,每一个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据;线程是进程的一条的一条独立路径,线程几乎不独立拥有资源,线程共享进程的资源,包括文件描述符等,线程共享相同的地址空间,因此以上的x是会被主线程和其他线程共享。

如何进行线程的同步

①可以使用信号量控制线程的执行顺序,比如sem_init初始化一个信号量;sem_post相当于原子操作的V操作;sem_wait相当于原子操作的P操作;

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

void *thread_function(void *arg);
sem_t bin_sem;
#define WORK_SIZE 1024
char work_area[WORK_SIZE];

int main(){
	int res;
	pthread_t a_thread;
	void *thread_result;
	res=sem_init(&bin_sem,0,0);//信号量初始化为0
	if(res!=0){
		printf("Semaphore initialization failed");
		exit(EXIT_FAILURE);
	}

	res=pthread_create(&a_thread,NULL,thread_function,NULL);
	if(res!=0){
		perror("Thread creation failed");
		exit(EXIT_FAILURE);
	}

	printf("Input some text. Enter 'end' to finish\n");
	while(strncmp("end",work_area,3)!=0){
		fgets(work_area,WORK_SIZE,stdin);
		sem_post(&bin_sem);
	}
	printf("\nWaiting for thread to finish\n");
	res=pthread_join(a_thread,&thread_result);
	if(res!=0){
		perror("Thread join failed");
		exit(EXIT_FAILURE);

	}
	printf("Thread join\n");
	sem_destroy(&bin_sem);//清除信号资源
	exit(EXIT_SUCCESS);

}
void * thread_function(void *arg){
	sem_wait(&bin_sem);
	while(strncmp("end",work_area,3)!=0){
		printf("You input %d characters\n",strlen(work_area)-1);
		sem_wait(&bin_sem);
	}
	
	pthread_exit(NULL);
}

②也可以使用互斥锁进行同步:pthread_mutex_init用来初始化一个互斥锁,pthread_mutex_lock、pthread_mutex_unlock进行加锁和解锁操作,pthread_mutex_destroy用来释放锁操作。
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL); ///< 初始化互斥锁
pthread_mutex_lock(&mutex); ///< 加锁
///< 操作公共资源
pthread_mutex_unlock(&mutex); ///< 解锁
pthread_mutex_destroy(&mutex); ///< 销毁互斥锁

请设计使用进程和线程实现相同目标的程序,请说明你对进程/线程应用范围的看法

使用进程(在C中使用fork):

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
	int x = 0;

	if (fork() == 0) {
		// 子进程
		x++;
		printf("子进程:x = %d\n", x);
	} else {
		// 父进程
		wait(NULL);
		printf("父进程:x = %d\n", x);
	}

	return 0;
}
[cch@aubin os]$ gcc demo.c
[cch@aubin os]$ ./a.out
子进程:x = 1
父进程:x = 0
[cch@aubin os]$ 

使用线程(在C中使用pthread):

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

int x = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *increment(void *arg) {
	pthread_mutex_lock(&mutex);
	x++;
	printf("线程:x = %d\n", x);
	pthread_mutex_unlock(&mutex);
	return NULL;
}

int main() {
	pthread_t thread1, thread2;

	pthread_create(&thread1, NULL, increment, NULL);
	pthread_create(&thread2, NULL, increment, NULL);

	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);

	printf("主线程:x = %d\n", x);

	return 0;
}
[cch@aubin os]$ ./a.out
线程:x = 1
线程:x = 2
主线程:x = 2
[cch@aubin os]$ 

进程的应用范围:
并行计算: 进程适用于需要充分利用多核处理器进行并行计算的场景。每个进程有独立的内存空间,可以并行执行,提高计算效率。
稳定性: 进程之间的独立性可以提高程序的稳定性。一个进程的崩溃不会影响其他进程的执行。
资源隔离: 进程之间有独立的内存空间,可以更好地实现资源隔离,确保一个进程的错误不会波及到其他进程。
线程的应用范围:
轻量级任务: 线程适用于需要执行相对轻量级任务的场景,例如I/O密集型任务。线程的创建和切换开销较小。
共享内存: 线程之间共享同一进程的内存空间,适合在同一应用程序内共享数据。
响应性: 线程可用于实现更快的响应时间,例如在GUI应用程序中响应用户输入。

<think>好的,用户想了解Linux线程的使用与实现。首先,我需要整理一下用户提供的引用内容,看看这些引用中有哪些关键点。根据引用[1],Linux中没有内核级别的线程接口,而是通过用户层的pthread库来模拟线程,封装了轻量级进程的系统调用。引用[2]提到Linux用struct TCB结构体描述线程,并需要将线程和进程解耦,同时设计调度算法。引用[3]和[4]涉及线程分离函数和线程安全等问题。 用户的问题集中在Linux线程的使用和实现,所以回答需要涵盖这两个方面。首先,应该解释线程Linux中的实现方式,即基于轻量级进程,用户层的pthread库封装系统调用。然后,讨论线程的管理,比如TCB结构体和调度。接着,使用方法部分需要包括创建线程、终止、同步机制(互斥锁、信号量)等。此外,线程安全和注意事项,比如避免在多线程中调用fork,以及线程分离函数pthread_detach的使用也很重要。 需要确保回答结构清晰,分点说明实现和使用,并引用提供的参考资料。同时,根据系统指令,数学公式要用$...$,独立公式用$$...$$,但在这个问题中可能用不上太多数学公式,不过如果有涉及的话需要注意格式。生成的相关问题要围绕线程的实现细节、使用方法、常见问题展开。 检查是否有遗漏的关键点,比如线程与进程的关系,线程的优缺点,以及实际应用中的注意事项。确保每个部分都有对应的引用支持,比如引用[1]说明pthread库的作用,引用[2]解释线程管理和调度,引用[3]和[4]涉及具体函数和线程安全。 最后,生成的相关问题需要引导用户深入理解线程的实现机制、使用方法和常见问题处理,比如线程与进程的区别、线程私有数据、线程池的实现等。确保问题覆盖用户可能感兴趣的各个方面。</think>### Linux线程的实现与使用 #### 一、Linux线程的实现机制 1. **用户层与内核的关系** 在Linux中,线程通过用户层的**原生线程库pthread**实现[^1]。内核本身没有直接提供线程接口,而是通过轻量级进程(LWP)模拟线程行为。pthread库封装了系统调用(如`clone()`),将线程创建、同步等操作转化为对LWP的管理。 2. **线程描述与组织** 内核通过`struct task_struct`描述进程/线程,而用户层线程库(如pthread)维护额外的数据结构(类似`struct TCB`)来记录线程属性(如栈、局部存储)[^2]。线程组内的所有线程共享进程资源(如地址空间、文件描述符),但拥有独立的执行流和栈空间。 3. **线程调度** Linux线程调度由内核负责,采用与进程相同的调度算法(如CFS)。线程的优先级、时间片等属性通过系统调用设置,例如: ```c pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param); ``` #### 二、线程的基本使用 1. **线程创建** 使用`pthread_create`接口创建线程,需指定线程函数和参数: ```c #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); ``` - 示例:主线程创建子线程 ```c void* thread_func(void* arg) { printf("Thread ID: %ld\n", (long)pthread_self()); return NULL; } pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); ``` 2. **线程终止** - 线程函数返回时自动终止。 - 调用`pthread_exit`显式退出: ```c void pthread_exit(void *retval); ``` 3. **线程同步** - **互斥锁(Mutex)** 避免多线程访问共享资源冲突: ```c pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mutex); // 临界区操作 pthread_mutex_unlock(&mutex); ``` - **信号量(Semaphore)** 控制资源访问数量: ```c sem_t sem; sem_init(&sem, 0, 1); // 初始值为1 sem_wait(&sem); // P操作 sem_post(&sem); // V操作 ``` 4. **线程分离** 默认线程为可连接(joinable),需通过`pthread_join`回收资源。若无需等待线程结束,可调用`pthread_detach`分离线程[^3]: ```c pthread_detach(pthread_self()); // 线程自行分离 ``` #### 三、关键问题与注意事项 1. **线程安全与竞争条件** 多线程操作全局变量或共享资源时需同步。例如,多个线程对同一变量累加可能因竞争导致结果错误[^4]。 2. **线程与`fork()`的冲突** `fork()`会复制调用线程的执行状态,但其他线程不会被复制。若父线程持有锁,子进程可能死锁。解决方式: - 调用`pthread_atfork`注册预处理函数。 - 避免在多线程程序中使用`fork()`[^4]。 3. **线程局部存储(TLS)** 使用`pthread_key_create`定义线程私有数据: ```c pthread_key_t key; pthread_key_create(&key, NULL); pthread_setspecific(key, data_ptr); // 设置线程私有数据 void* data = pthread_getspecific(key); // 获取数据 ``` #### 四、线程的优缺点 | 优点 | 缺点 | |------|------| | 轻量级上下文切换 | 调试复杂度高 | | 共享内存提升通信效率 | 需严格处理同步问题 | | 适合I/O密集型任务 | 线程崩溃可能导致进程退出 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值