L
O
A
D
I
N
G

Pthreads


Pthreads

创建和终止

本节展示了两个最基本的线程管理函数——创建与终止线程。

  • pthread.h头文件的引入
  • pthread_create创建线程
  • pthread_exit终止线程

欢迎来到Posix Threads的世界。

为了编写Pthreads程序。首先,我们应包含头文件<pthread.h>。这个头文件包含了Pthreads程序的必要函数。

同时,我们也需要引入这些函数实现线程化的功能。

函数说明

创建线程

int pthread_create(pthread_t * thread, 
				   const pthread_attr_t * attr,
				   void * (*start_routine)(void *), 
				   void *arg);
	
参数:
thread - 指向线程的指针。
attr - 指向线程属性的指针。可设置为NULL。
start_routine - 用于线程化的函数指针。
arg - 指向函数参数的指针。
	
返回值:线程标识id

pthread_create是POSIX线程库中的一个函数,用于创建一个新的线程。它接受四个参数:

  1. pthread_t * thread:这是一个指向pthread_t类型的指针,用于存储新创建的线程的标识符。
  2. const pthread_attr_t * attr:这是一个指向pthread_attr_t类型的指针,用于设置新线程的属性。如果设置为NULL,则使用默认属性。
  3. void * (*start_routine)(void *):这是一个函数指针,指向新线程的入口点函数。这个函数将在新线程中执行。
  4. void *arg:这是传递给入口点函数的参数。

函数返回0表示成功,非0表示失败。

退出线程

void pthread_exit(void *retval);
	
参数:
retval - 指向线程返回值的指针。

pthread_exit(void *retval) 是一个用于终止线程的函数,它接受一个 void * 类型的参数 retval。当线程调用此函数时,它将立即返回,并将 retval 作为线程的退出状态传递给操作系统。

pthread_exit(void *retval); 是一个用于终止线程的函数,它接受一个指向返回值的指针作为参数。当线程调用此函数时,它将立即终止,并将指定的返回值传递给调用者。

输出结果

In main: creating thread 0
In main: creating thread 1
Hello World!
In main: creating thread 2
In main: creating thread 3
Hello World!
Hello World!
Hello World!

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h>
#define NUM_THREADS	4

void *PrintHello(void *null) 
{ 
	printf("Hello World!\n"); 
	pthread_exit(NULL); 
} 
 
int main (int argc, char *argv[]) 
{ 
	pthread_t threads[NUM_THREADS]; 
	int rc=-1, t; 
	for(t=0; t<NUM_THREADS; t++){ 
		printf("In main: creating thread %d\n", t); 
		//your code here
		rc = pthread_create(&threads[t], NULL, PrintHello, NULL); 
		//end of your code
		if (rc){ 
			printf("ERROR; return code from pthread_create() is %d\n", rc); 
			exit(-1); 
		} 
	} 
	pthread_exit(NULL); 
}

向线程传递参数(1)

本节展示了最基本的线程创建,以及向线程传递单个参数的方法。

  • pthread_create创建线程
  • pthread_exit终止线程

本例用pthread_create创建了4个线程。

用于线程化的线程接收唯一一个参数。

每一个线程都会打印一条表明自己线程id的消息,然后调用pthread_exit终止线程。

输出结果

由于并行程序执行顺序的不确定性,你的结果的顺序可能和这个结果不一致。
为了比较输出结果,请将环境设置中的并行核数调为1。
本实验暂不支持性能评测。

In main: creating thread 0
In main: creating thread 1
Hello World! It’s me, thread #0!
In main: creating thread 2
Hello World! It’s me, thread #1!
In main: creating thread 3
Hello World! It’s me, thread #2!
Hello World! It’s me, thread #3!

#include <pthread.h> 
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS	4

void *PrintHello(void *threadid) 
{ 
	int tid; 
	tid = *((int*)threadid); 
	printf("Hello World! It's me, thread #%d!\n", tid); 
	free(threadid);
	threadid = NULL;
	pthread_exit(NULL); 
} 
 
int main (int argc, char *argv[]) 
{ 
	pthread_t threads[NUM_THREADS]; 
	int rc=-1, t; 
	for(t=0; t<NUM_THREADS; t++){ 
		printf("In main: creating thread %d\n", t); 
		int* id = (int*)malloc(sizeof(int));
		*id = t;
		//your code here
		rc = pthread_create(&threads[t], NULL, PrintHello, (void *)id); 
		//end of your code
		if (rc){ 
			printf("ERROR; return code from pthread_create() is %d", rc); 
			exit(-1); 
		} 
	} 
	pthread_exit(NULL); 
}

向线程传递参数(2)

本节展示了向线程传递非空参数的方法。

  • pthread_create创建线程,接收多参数

很多时候,用于线程化的函数需要接收不止一个参数。
当多个参数需要被传递时,可以通过定义一个结构体包含所有要传的参数,然后用pthread_create传递一个指向改结构体的指针,来打破传递参数的个数的限制。

输出结果

由于并行程序执行顺序的不确定性,你的结果的顺序可能和这个结果不一致。

msg: apple on thread 0
msg: cat on thread 2
msg: banana on thread 1
msg: dog on thread 3

#include <pthread.h> 
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS   4

struct thread_data{ 
	int  thread_id; 
	const char *message; 
}thread_data_array[NUM_THREADS]; 
 
void *Print(void *threadarg) 
{  
	struct thread_data *my_data; 
	my_data = (struct thread_data *) threadarg; 
	int taskid;
	const char* msg;
	taskid = my_data->thread_id; 
	msg = my_data->message;
	printf("msg: %s on thread %d\n", msg, taskid);
	pthread_exit(NULL);
} 
 
int main (int argc, char *argv[]) 
{
	pthread_t threads[NUM_THREADS]; 
	int t, rc;
	const char* messages[NUM_THREADS] = {"apple", "banana", "cat", "dog"};
	for(t = 0; t < NUM_THREADS; t++)
	{
		thread_data_array[t].thread_id = t; 
		thread_data_array[t].message = messages[t]; 
		//your code here
		rc = pthread_create(&threads[t], NULL, Print, (void *) &thread_data_array[t]); 
		//end of your code
		if (rc)
		{ 
			printf("ERROR; return code from pthread_create() is %d", rc); 
			exit(-1); 
		} 	
	}
	pthread_exit(NULL); 
}

线程属性 - 线程连接和分离(1)

本节展示了线程连接和分离的基本知识。

  • pthread_join主线程挂起直至目标进程返回
  • pthread_attr_init初始化属性对象
  • pthread_attr_destroy销毁属性对象
  • pthread_attr_setdetachstate设置属性对象的分离状态

当一个线程被创建,它有一个属性定义了它是可连接的(joinable)还是分离的(detached)。只有是可连接的线程才能被连接(joined),若果创建的线程是分离的,则不能连接。
pthread_join函数阻塞主线程直到指定的线程终止。“连接”是一种在线程间完成同步的方法。
pthread_detach函数可以显式用于分离线程。

使用pthread_create的attr参数可以显式的创建可连接或分离的线程,典型四步如下:

  1. 声明一个pthread_attr_t数据类型的线程属性变量
  2. pthread_attr_init初始化改属性变量
  3. pthread_attr_setdetachstate设置可分离状态属性
  4. 完了后,用pthread_attr_destroy释放属性所占用的库资源

函数说明

主线程挂起直至目标进程返回

int pthread_join(pthread_t thread, void **retval);

参数:
thread - 线程id,标识唯一线程。
retval - 用户定义的指针,用来存储被等待线程的返回值。

返回值:0代表成功。若失败,返回的则是错误号。

pthread_join 是一个用于等待线程结束的函数,它接受两个参数:一个是要等待的线程标识符 pthread_t thread,另一个是指向返回值的指针 void **retval。当线程结束时,pthread_join 会将线程的返回值存储在 retval 指向的内存中。

函数原型如下:

int pthread_join(pthread_t thread, void **retval);

参数说明:

  • thread:要等待的线程标识符。
  • retval:指向返回值的指针。如果线程正常结束,该指针将被设置为线程的返回值;如果线程被其他线程阻塞,该指针将被设置为NULL。

返回值:

  • 如果成功等待线程结束并获取返回值,返回0;
  • 如果线程已经结束,返回0;
  • 如果发生错误,返回非0值。

初始化属性对象

int pthread_attr_init(pthread_attr_t *attr);

int pthread_attr_init(pthread_attr_t *attr); 是一个用于初始化线程属性的函数。它接受一个指向 pthread_attr_t 类型的指针作为参数,该指针用于存储线程属性的结构体。如果函数成功执行,它将返回0;否则,返回非0值。

以下是一个简单的示例代码,演示如何使用 pthread_attr_init 函数:

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

int main() {
 pthread_attr_t attr;
 int result = pthread_attr_init(&attr);

 if (result == 0) {
     printf("线程属性初始化成功\n");
     // 在这里可以对线程属性进行进一步的配置和设置

     // 在完成线程属性的使用后,记得释放资源
     pthread_attr_destroy(&attr);
 } else {
     printf("线程属性初始化失败\n");
 }

 return 0;
}

请注意,在使用完线程属性后,需要调用 pthread_attr_destroy 函数来释放相关资源。

销毁属性对象

int pthread_attr_destroy(pthread_attr_t *attr);

参数:
attr - 指向线程属性对象的指针。

返回值:0代表成功。若失败,返回的则是错误号。

int pthread_attr_destroy(pthread_attr_t *attr); 是一个用于销毁线程属性的函数。它接受一个指向 pthread_attr_t 类型的指针作为参数,该指针指向要销毁的线程属性结构体。如果函数成功执行,它将返回0;否则,返回非0值。

以下是一个简单的示例代码,演示如何使用 pthread_attr_destroy 函数:

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

int main() {
 pthread_attr_t attr;
 int result = pthread_attr_init(&attr);

 if (result == 0) {
     // 在这里可以对线程属性进行进一步的配置和设置

     // 在完成线程属性的使用后,记得销毁它
     result = pthread_attr_destroy(&attr);
     if (result == 0) {
         printf("线程属性销毁成功\n");
     } else {
         printf("线程属性销毁失败\n");
     }
 } else {
     printf("线程属性初始化失败\n");
 }

 return 0;
}

设置属性对象的分离状态

int pthread_attr_setdetachstate(const pthread_attr_t *attr,
                                int *detachstate);
参数:
attr - 指向线程属性对象的指针。
detachstate - 设置可分离(PTHREAD_CREATE_DETACHED)或可连接属性(PTHREAD_CREATE_JOINABLE)。

pthread_attr_setdetachstate 是一个用于设置线程属性的函数,它允许你指定线程在创建后是否应该自动分离。该函数接受两个参数:

  1. const pthread_attr_t *attr:指向要设置的属性对象的指针。这个对象包含了线程的属性信息,例如堆栈大小、优先级等。
  2. int *detachstate:指向一个整数变量的指针,用于存储设置的分离状态。你可以使用以下值之一来指定分离状态:
    • PTHREAD_CREATE_JOINABLE:线程可以在其他线程中被连接(join),即主线程可以等待子线程完成执行。这是默认的分离状态。
    • PTHREAD_CREATE_DETACHED:线程在创建后将自动分离,即主线程无法等待其完成执行。

函数返回0表示成功设置了分离状态,非零值表示出现错误。

下面是一个示例代码,演示如何使用 pthread_attr_setdetachstate 函数来设置线程的分离状态:

#include <pthread.h>

int main() {
    pthread_attr_t attr;
    int detachstate = PTHREAD_CREATE_JOINABLE; // 设置分离状态为可连接

    // 初始化属性对象
    pthread_attr_init(&attr);

    // 设置分离状态
    int result = pthread_attr_setdetachstate(&attr, &detachstate);
    if (result != 0) {
        // 处理错误情况
        printf("Failed to set detach state\n");
    } else {
        // 分离状态设置成功,继续执行其他操作
        // ...
    }

    // 销毁属性对象
    pthread_attr_destroy(&attr);

    return 0;
}

请注意,在使用 pthread_attr_setdetachstate 函数之前,你需要先初始化属性对象,并在使用完毕后销毁它。

运行结果

由于并行程序执行顺序的不确定性,你的结果的顺序可能和这个结果不一致。
为了比较输出结果,请将环境设置中的并行核数调为1。
本实验暂不支持性能评测。

Creating thread 0
Creating thread 1
Creating thread 2
Thread 0 is running
Creating thread 3
Thread 1 is running
Thread 2 is running
Completed join with thread 0
Thread 3 is running
Completed join with thread 1
Completed join with thread 2
Completed join with thread 3

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h>
#define NUM_THREADS	 4 
 
void *Print(void *threadid) 
{ 
	printf("Thread %d is running\n", *(int*)threadid); 
	free(threadid);
	threadid = NULL;
	pthread_exit(NULL); 
} 
 
int main (int argc, char *argv[]) 
{ 
	pthread_t thread[NUM_THREADS]; 
	pthread_attr_t attr; 
	int rc, t; 
 
	pthread_attr_init(&attr); 
	
	//your code here
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 
	//end of your code
	
	for(t=0; t<NUM_THREADS; t++) 
	{ 
		int* id = (int*)malloc(sizeof(int));
		*id = t;
		printf("Creating thread %d\n", t); 
		rc = pthread_create(&thread[t], &attr, Print, (void*)id);	
		if (rc) 
		{ 
			printf("ERROR; return code from pthread_create() is %d\n", rc); 
			exit(-1); 
		} 
	} 
	
	pthread_attr_destroy(&attr); 
	for(t=0; t<NUM_THREADS; t++) 
	{ 
		//your code here
		rc = pthread_join(thread[t], NULL); 
		//end of your code
		
		if (rc) 
		{ 
			printf("ERROR; return code from pthread_join() is %d\n", rc); 
			exit(-1); 
		} 
		printf("Completed join with thread %d\n", t); 
	} 
 
	pthread_exit(NULL); 
} 

线程属性 - 栈管理

本节展示了获取与设置当前线程的栈空间的方法。

  • pthread_attr_getstacksize获取当前线程的栈空间
  • pthread_attr_setstacksize设置当前线程的栈空间

POSIX标准并没有指定线程栈的大小,依赖于实现并随实现变化。

很容易超出默认的栈大小,常见结果:程序终止或者数据损坏。

安全和可移植的程序应该不依赖于默认的栈限制,但是取而代之的是用pthread_attr_setstacksize分配足够的栈大小。

pthread_attr_getstackaddrpthread_attr_setstackaddr函数可以被程序用于将栈设置在指定的内存区域。

函数说明

获取当前线程的栈空间

int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,
                              size_t *restrict stacksize);

pthread_attr_getstacksize 是一个用于获取线程属性中栈大小的函数。它接受两个参数:一个指向 pthread_attr_t 类型的指针,表示要查询的线程属性;另一个是指向 size_t 类型的指针,用于存储查询到的栈大小。

函数原型如下:

int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);

该函数返回一个整数,如果成功获取到栈大小,则返回0;否则返回非零值。

以下是一个示例代码,演示如何使用 pthread_attr_getstacksize 函数来获取线程属性中的栈大小:

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

int main() {
 pthread_attr_t attr;
 size_t stacksize;

 // 初始化线程属性对象
 pthread_attr_init(&attr);

 // 获取线程属性中的栈大小
 int result = pthread_attr_getstacksize(&attr, &stacksize);
 if (result == 0) {
     printf("Stack size: %zu\n", stacksize);
 } else {
     printf("Failed to get stack size\n");
 }

 // 销毁线程属性对象
 pthread_attr_destroy(&attr);

 return 0;
}

在上述示例中,我们首先创建了一个 pthread_attr_t 类型的变量 attr,然后使用 pthread_attr_init 函数进行初始化。接下来,调用 pthread_attr_getstacksize 函数来获取线程属性中的栈大小,并将结果存储在 stacksize 变量中。最后,根据返回值判断是否成功获取到栈大小,并打印相应的信息。最后,记得使用 pthread_attr_destroy 函数销毁线程属性对象。

设置当前线程的栈空间

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

参数:
attr - 指向线程属性的指针
stacksize - 栈空间大小

返回值:0代表成功。若失败,返回的则是错误号。

注:restrict为C语言保留字,用于编译器优化。后同。

pthread_attr_setstacksize 是一个用于设置线程属性中栈大小的函数。它接受两个参数:一个指向 pthread_attr_t 类型的指针,表示要设置的线程属性;另一个是 size_t 类型的变量,表示要设置的栈大小。

函数原型如下:

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

该函数返回一个整数,如果成功设置栈大小,则返回0;否则返回非零值。

以下是一个示例代码,演示如何使用 pthread_attr_setstacksize 函数来设置线程属性中的栈大小:

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

int main() {
 pthread_attr_t attr;
 size_t stacksize = 1024 * 1024; // 设置栈大小为1MB

 // 初始化线程属性对象
 pthread_attr_init(&attr);

 // 设置线程属性中的栈大小
 int result = pthread_attr_setstacksize(&attr, stacksize);
 if (result == 0) {
     printf("Stack size set successfully
");
 } else {
     printf("Failed to set stack size
");
 }

 // 销毁线程属性对象
 pthread_attr_destroy(&attr);

 return 0;
}

在上述示例中,我们首先创建了一个 pthread_attr_t 类型的变量 attr,然后使用 pthread_attr_init 函数进行初始化。接下来,调用 pthread_attr_setstacksize 函数来设置线程属性中的栈大小,并将结果存储在 stacksize 变量中。最后,根据返回值判断是否成功设置栈大小,并打印相应的信息。最后,记得使用 pthread_attr_destroy 函数销毁线程属性对象。

运行结果

由于并行程序执行顺序的不确定性,你的结果的顺序可能和这个结果不一致。
为了比较输出结果,请将环境设置中的并行核数调为1。
本实验暂不支持性能评测。

Default stack size = 0
Amount of stack needed per thread = 9000000
Creating threads with stack size = 9000000 bytes
Created 4 threads.
Thread 1: stack size = 9000000 bytes
Thread 2: stack size = 9000000 bytes
Thread 0: stack size = 9000000 bytes
Thread 3: stack size = 9000000 bytes

#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h>
#define NUMTHREADS 4 
#define N 1000 
#define MEGEXTRA 1000000 
	
pthread_attr_t attr; 
	
void *dowork(void *threadid) 
{
	//The 2-dim array takes up sizeof(double)*N*N bytes.
	double A[N][N]; 
	int i,j,tid; 
	size_t mystacksize; 
 
	tid = *(int*)threadid; 
	pthread_attr_getstacksize (&attr, &mystacksize); 
	printf("Thread %d: stack size = %li bytes \n", tid, mystacksize); 
	for (i=0; i<N; i++) 
		for (j=0; j<N; j++) 
			A[i][j] = ((i*j)/3.452) + (N-i); 
	
	free(threadid);
	threadid = NULL;
	pthread_exit(NULL); 
} 
	
int main(int argc, char *argv[]) 
{ 
	pthread_t threads[NUMTHREADS]; 
	size_t stacksize; 
	int rc, t; 
	
	pthread_attr_init(&attr);
	
	//your code here
	pthread_attr_getstacksize(&attr, &stacksize); 
	//end of your code
	printf("Default stack size = %li\n", stacksize); 
	
	stacksize = sizeof(double)*N*N+MEGEXTRA; 
	printf("Amount of stack needed per thread = %li\n",stacksize); 
	
	//your code here
	pthread_attr_setstacksize(&attr, stacksize); 
	//end of your code
	printf("Creating threads with stack size = %li bytes\n",stacksize); 
	
	for(t=0; t<NUMTHREADS; t++){ 
		int* id = (int*)malloc(sizeof(int));
		*id = t;
		rc = pthread_create(&threads[t], &attr, dowork, (void *)id); 
		if (rc){ 
			printf("ERROR; return code from pthread_create() is %d\n", rc); 
			exit(-1); 
		} 
	} 
	printf("Created %d threads.\n", t); 
	pthread_exit(NULL); 
}

互斥锁(1)

  • 定义一个互斥锁
  • pthread_mutex_init互斥锁的初始化
  • pthread_mutex_destroy互斥锁的销毁
  • pthread_mutex_lock加锁
  • pthread_mutex_unlock解锁

互斥锁(Mutex)是“mutual exclusion”的缩写。互斥锁是实现线程同步,和保护同时写共享数据的主要方法
互斥锁对共享数据的保护就像一把锁。在Pthreads中,任何时候仅有一个线程可以锁定互斥锁,因此,当多个线程尝试去锁定该互斥锁时仅有一个会成功。直到锁定互斥锁的线程解锁互斥锁后,其他线程才可以去锁定互斥锁。线程必须轮着访问受保护数据。
互斥锁可以防止“竞争”条件。

用互斥锁的典型顺序如下:

  1. 创建和初始一个互斥锁
  2. 多个线程尝试去锁定该互斥锁
  3. 仅有一个线程可以成功锁定改互斥锁
  4. 锁定成功的线程做一些处理
  5. 线程解锁该互斥锁
  6. 另外一个线程获得互斥锁,重复上述过程
  7. 最后销毁互斥锁

函数说明:

定义一个互斥锁

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
PTHREAD_MUTEX_INITIALIZER是定义在头文件中的宏,能用于初始化静态分配空间的互斥锁。

这是C语言中用于初始化互斥锁的代码。pthread_mutex_t 是互斥锁的类型,PTHREAD_MUTEX_INITIALIZER 是一个宏,用于初始化互斥锁。

解析:

  1. pthread_mutex_t mutex;:声明一个互斥锁变量 mutex
  2. = PTHREAD_MUTEX_INITIALIZER;:使用 PTHREAD_MUTEX_INITIALIZER 宏来初始化互斥锁。

代码:

#include <pthread.h>

int main() {
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    // 其他代码...
    return 0;
}

互斥锁的初始化

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                       const pthread_mutexattr_t *restrict attr);

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 是一个用于初始化互斥锁的函数。它接受两个参数:

  1. pthread_mutex_t *restrict mutex:指向要初始化的互斥锁对象的指针。
  2. const pthread_mutexattr_t *restrict attr:指向互斥锁属性对象的指针,该对象定义了互斥锁的行为和属性。如果设置为NULL,则使用默认的属性。

函数返回一个整数,表示操作的结果。如果成功,返回0;否则,返回一个非零的错误码。

以下是一个示例代码,演示如何使用 pthread_mutex_init 函数来初始化一个互斥锁:

#include <pthread.h>

int main() {
    pthread_mutex_t myMutex;
    int result = pthread_mutex_init(&myMutex, NULL);
    if (result == 0) {
        // 互斥锁初始化成功
        // 在这里可以使用互斥锁进行线程同步操作
        // ...

        // 释放互斥锁资源
        pthread_mutex_destroy(&myMutex);
    } else {
        // 互斥锁初始化失败,处理错误情况
        // ...
    }

    return 0;
}

请注意,在使用完互斥锁后,需要调用 pthread_mutex_destroy 函数来释放互斥锁资源,以避免内存泄漏。

互斥锁的销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

int pthread_mutex_destroy(pthread_mutex_t *mutex); 是一个用于销毁互斥锁的函数。它接受一个指向 pthread_mutex_t 类型的指针作为参数,该指针指向要销毁的互斥锁对象。

当调用此函数时,它将释放互斥锁所占用的系统资源,并使互斥锁处于未锁定状态。在销毁互斥锁之前,必须先确保没有任何线程正在使用该互斥锁进行同步操作。

以下是一个示例代码,演示如何使用 pthread_mutex_destroy 函数来销毁互斥锁:

#include <pthread.h>

// 创建互斥锁
pthread_mutex_t myMutex;
pthread_mutex_init(&myMutex, NULL);

// 执行一些需要同步的操作...

// 销毁互斥锁
pthread_mutex_destroy(&myMutex);

请注意,在使用 pthread_mutex_destroy 函数之前,必须确保互斥锁已经被正确地初始化,并且没有其他线程正在使用它进行同步操作。

加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_lock(pthread_mutex_t *mutex); 是一个用于锁定互斥锁的函数。它接受一个指向 pthread_mutex_t 类型的指针作为参数,该指针指向要锁定的互斥锁对象。

当调用此函数时,它将尝试获取互斥锁的所有权。如果互斥锁已经被其他线程锁定,则当前线程将被阻塞,直到互斥锁被释放。如果成功获取到互斥锁的所有权,函数将返回0;否则,返回非零错误码。

以下是一个示例代码,演示如何使用 pthread_mutex_lock 函数来锁定互斥锁:

#include <pthread.h>

// 创建互斥锁
pthread_mutex_t myMutex;
pthread_mutex_init(&myMutex, NULL);

// 执行一些需要同步的操作...

// 锁定互斥锁
int result = pthread_mutex_lock(&myMutex);
if (result == 0) {
 // 互斥锁已成功锁定,可以执行临界区代码
 // ...
} else {
 // 互斥锁锁定失败,处理错误情况
 // ...
}

// 解锁互斥锁
pthread_mutex_unlock(&myMutex);

// 销毁互斥锁
pthread_mutex_destroy(&myMutex);

请注意,在使用 pthread_mutex_lock 函数之前,必须确保互斥锁已经被正确地初始化,并且没有其他线程正在使用它进行同步操作。在完成对互斥锁的使用后,应该使用 pthread_mutex_unlock 函数来释放互斥锁的所有权,并最终使用 pthread_mutex_destroy 函数来销毁互斥锁对象。

解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex); 是一个用于解锁互斥锁的函数。它接受一个指向 pthread_mutex_t 类型的指针作为参数,该指针指向要解锁的互斥锁对象。

当调用此函数时,它将释放互斥锁的所有权,并允许其他线程获取互斥锁进行同步操作。如果成功解锁互斥锁,函数将返回0;否则,返回非零错误码。

以下是一个示例代码,演示如何使用 pthread_mutex_unlock 函数来解锁互斥锁:

#include <pthread.h>

// 创建互斥锁
pthread_mutex_t myMutex;
pthread_mutex_init(&myMutex, NULL);

// 执行一些需要同步的操作...

// 锁定互斥锁
pthread_mutex_lock(&myMutex);

// 执行临界区代码
// ...

// 解锁互斥锁
pthread_mutex_unlock(&myMutex);

// 销毁互斥锁
pthread_mutex_destroy(&myMutex);

请注意,在使用 pthread_mutex_unlock 函数之前,必须确保互斥锁已经被正确地初始化,并且当前线程已经获得了互斥锁的所有权。在完成对互斥锁的使用后,应该使用 pthread_mutex_destroy 函数来销毁互斥锁对象。

运行结果:

0
1
2
3
4
5
6
7
8
9
10

#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h>  
#include <stdlib.h>
  
int i;  
pthread_mutex_t mutex1;  
  
void* readfunction(void*);  
void* writefunction(void*);  
  
int main()  
{  
	pthread_t thread1,thread2;  
  
	i = 0;  
	//your code here
	pthread_mutex_init(&mutex1,NULL); 
	//end of your code
	pthread_create(&thread1,NULL,readfunction,NULL);
	pthread_create(&thread2,NULL,writefunction,NULL);  
	pthread_join(thread1,NULL);  
	pthread_join(thread2,NULL);
	//your code here
	pthread_mutex_destroy(&mutex1);
	//end of your code
	return 0;  
}  
  
void* readfunction(void* args)  
{  
	while(1)  
	{  
		usleep(1500);
		if(i >= 10)  
			break;  
		pthread_mutex_lock(&mutex1);  
		printf("%d\n",i);  
		pthread_mutex_unlock(&mutex1);  
	}  
}  
  
void* writefunction(void* args)  
{  
	while(i < 10)  
	{  
		pthread_mutex_lock(&mutex1);  
		i++;  
		pthread_mutex_unlock(&mutex1);  
		usleep(2000);  
	}  
}

互斥锁(2)

  • pthread_mutex_trylockpthread_mutex_lock的非阻塞版本,调用后立即返回。

函数原型

int pthread_mutex_trylock(pthread_mutex_t *mutex); 

int pthread_mutex_trylock(pthread_mutex_t *mutex); 是一个用于尝试获取互斥锁的函数。它接受一个指向 pthread_mutex_t 类型的指针作为参数,该指针指向要锁定的互斥锁对象。

如果成功获取到互斥锁的所有权,函数将返回0;否则,返回非零错误码。这个函数与 pthread_mutex_lock 函数类似,但不会阻塞当前线程,而是立即返回结果。这在某些情况下可能更有用,例如在高并发场景下,可以避免不必要的线程阻塞。

运行结果

pthread b trylock
the main close

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

pthread_mutex_t mutex;

void *thread_a(void *arg)
{
    pthread_mutex_lock(&mutex);
    sleep(1);
    pthread_mutex_unlock(&mutex);
}

void *thread_b(void *arg)
{
    while(/*your code here*/pthread_mutex_trylock(&mutex))
    {
        printf("pthread b trylock\n");
        sleep(1);
    }
    pthread_mutex_unlock(&mutex);
}

int main(int argc,char **argv)
{
    pthread_t tid_a,tid_b;
    pthread_mutex_init(&mutex,NULL);
	pthread_create(&tid_a,NULL,thread_a,NULL);
    pthread_create(&tid_b,NULL,thread_b,NULL);
    pthread_join(tid_a, NULL);
    pthread_join(tid_b, NULL);

    printf("the main close\n");

    return 0;
}

条件变量(1)

  • 条件变量的初始化
  • pthread_cond_init创建条件变量
  • pthread_cond_destroy删除条件变量
  • pthread_cond_wait等待条件变量为真
  • pthread_cond_signal解锁阻塞在条件变量上的进程

实验步骤

条件变量是用来通知共享数据状态信息的。
条件变量机制允许进程停止当前的执行,让出CPU时间,直到条件为真。
每个条件变量必须与一个特定的互斥量相关联以避免死锁。

pthread_cond_wait总和一个互斥锁结合使用。
在调用pthread_cond_wait前要先获取锁。
pthread_cond_wait函数执行时先自动释放指定的锁,然后等待条件变量的变化。在函数调用返回之前,自动将指定的互斥量重新锁住。
调用pthread_cond_signal后要立刻释放互斥锁。

函数说明

初始化

pthread_cond_t cod = PTHREAD_COND_INITIALIZER;

pthread_cond_t cod = PTHREAD_COND_INITIALIZER; 这行代码是用于初始化一个条件变量 cod。条件变量是一种同步原语,用于在多线程环境中实现线程间的同步和通信。

在这段代码中,PTHREAD_COND_INITIALIZER 是一个宏定义,它表示一个已经初始化的条件变量对象。通过将其赋值给 cod,我们可以将这个条件变量对象与 cod 关联起来。

使用条件变量可以使得多个线程等待某个条件成立时才继续执行。例如,当一个线程需要等待另一个线程完成某个任务后才能继续执行时,可以使用条件变量来实现这种同步机制。

以下是一个示例代码片段,演示如何使用条件变量进行线程同步:

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

// 全局变量作为条件变量的标识符
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void* thread1(void* arg) {
 // 线程1等待条件变量满足
 pthread_cond_wait(&cond, NULL);
 printf("Thread 1: Condition satisfied!\n");
 return NULL;
}

void* thread2(void* arg) {
 // 线程2等待条件变量满足
 pthread_cond_wait(&cond, NULL);
 printf("Thread 2: Condition satisfied!\n");
 return NULL;
}

int main() {
 pthread_t t1, t2;

 // 创建两个线程并启动
 pthread_create(&t1, NULL, thread1, NULL);
 pthread_create(&t2, NULL, thread2, NULL);

 // 主线程等待一段时间,模拟其他操作
 sleep(2);

 // 发送信号通知条件变量已满足
 pthread_cond_signal(&cond);
 pthread_cond_signal(&cond);

 // 等待线程结束
 pthread_join(t1, NULL);
 pthread_join(t2, NULL);

 return 0;
}

在上面的示例中,我们创建了两个线程 thread1thread2,它们都等待条件变量 cond 被满足。在主线程中,我们通过调用 pthread_cond_signal 函数来发送信号,通知条件变量已满足。这样,等待的线程就可以继续执行了。

创建条件变量

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *condattr);

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *condattr); 是一个用于初始化条件变量的函数。它接受两个参数:

  1. pthread_cond_t *cond:指向要初始化的条件变量的指针。
  2. pthread_condattr_t *condattr:指向条件变量属性的指针,可以为 NULL。如果为 NULL,则使用默认属性。

函数返回一个整数,表示操作的结果。如果成功,返回 0;否则,返回错误码。

删除条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_destroy(pthread_cond_t *cond); 是一个用于销毁条件变量的函数。它接受一个指向要销毁的条件变量的指针作为参数。

函数返回一个整数,表示操作的结果。如果成功,返回 0;否则,返回错误码。

等待条件变量为真

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 是一个用于线程等待条件的函数。它接受两个参数:

  1. pthread_cond_t *restrict cond:指向要等待的条件变量的指针。
  2. pthread_mutex_t *restrict mutex:指向互斥锁的指针,用于保护条件变量。

函数返回一个整数,表示操作的结果。如果成功,返回 0;否则,返回错误码。

解锁阻塞在条件变量上的进程

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_signal(pthread_cond_t *cond); 是一个用于唤醒等待条件变量的线程的函数。它接受一个指向条件变量的指针作为参数。

函数返回一个整数,表示操作的结果。如果成功,返回 0;否则,返回错误码。

运行结果

Counter value functionCount1: 1
Counter value functionCount2: 2
Counter value functionCount1: 3
Counter value functionCount2: 4
Counter value functionCount2: 5
Counter value functionCount2: 6
Counter value functionCount2: 7
Counter value functionCount2: 8
Counter value functionCount1: 9
Counter value functionCount2: 10
Counter value functionCount1: 11

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

pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t condition_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condition_cond  = PTHREAD_COND_INITIALIZER;

void *functionCount1(void*);
void *functionCount2(void*);
int  count = 0;
#define COUNT_DONE  10
#define COUNT_HALT1  3
#define COUNT_HALT2  6

int main()
{
   pthread_t thread1, thread2;
   pthread_create( &thread1, NULL, &functionCount1, NULL);
   pthread_create( &thread2, NULL, &functionCount2, NULL);
   pthread_join( thread1, NULL);
   pthread_join( thread2, NULL);

   exit(0);
}

void *functionCount1(void* args)
{
   for(;;)
   {
      pthread_mutex_lock( &condition_mutex );
      while( count >= COUNT_HALT1 && count <= COUNT_HALT2 )
      {
		//your code here
        pthread_cond_wait( &condition_cond, &condition_mutex );
		//end of your code
      }
      pthread_mutex_unlock( &condition_mutex );

      pthread_mutex_lock( &count_mutex );
      count++;
      printf("Counter value functionCount1: %d\n",count);
      pthread_mutex_unlock( &count_mutex );

      if(count >= COUNT_DONE) return(NULL);
    }
}

void *functionCount2(void* args)
{
    for(;;)
    {
       pthread_mutex_lock( &condition_mutex );
       if( count < COUNT_HALT1 || count > COUNT_HALT2 )
       {
        //your code here
        pthread_cond_signal( &condition_cond );
		//end of your code
       }
       pthread_mutex_unlock( &condition_mutex );

       pthread_mutex_lock( &count_mutex );
       count++;
       printf("Counter value functionCount2: %d\n",count);
       pthread_mutex_unlock( &count_mutex );

       if(count >= COUNT_DONE) return(NULL);
    }
}

条件变量(2)

本节继续展示条件变量的相关方法

  • pthread_cond_broadcast解除所有被某个条件变量阻塞的进程

pthread_cond_broadcast可用于解除所有被某个条件变量阻塞的进程
pthread_cond_signal函数相似,pthread_cond_broadcast函数执行时先自动释放指定的锁,然后等待条件变量的变化。在函数调用返回之前,自动将指定的互斥量重新锁住

函数说明

int pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_broadcast是一个POSIX线程库函数,用于唤醒所有等待某个条件变量的线程。它的原型如下:

参数:

  • cond:指向要广播的条件变量的指针

返回值:

  • 成功时返回0;失败时返回错误码

运行结果

thread 1 waiting for signal
thread 2 waiting for signal
main thread broadcasts signal
thread 2 receives signal
thread 1 receives signal

#include <pthread.h>  
#include <stdio.h>  
#include <unistd.h>  
#include <stdlib.h>
   
static pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER;  
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;  
  
static void* func_1(void* arg)  
{  
    pthread_mutex_lock(&mtx);  
    printf("thread 1 waiting for signal\n");
    pthread_cond_wait(&cond, &mtx);
	printf("thread 1 receives signal\n");
    pthread_mutex_unlock(&mtx);  
      
    return NULL;  
}  
  
static void* func_2(void* arg)
{  
      
    pthread_mutex_lock(&mtx);  
    printf("thread 2 waiting for signal\n");
    pthread_cond_wait(&cond, &mtx); 
	printf("thread 2 receives signal\n");
    pthread_mutex_unlock(&mtx);  
      
    return NULL;  
}  
  
  
int main()  
{  
    pthread_t tid1, tid2;  
  
    pthread_create(&tid1, NULL, func_1, NULL);  
    pthread_create(&tid2, NULL, func_2, NULL);  
  
  	sleep(1);
  	printf("main thread broadcasts signal\n");
	//your code here
    pthread_cond_broadcast(&cond);  
	//end of your code
  
    pthread_join(tid1, NULL);  
    pthread_join(tid2, NULL);  
    
    return 0;  
} 

障碍同步

本节展示了障碍同步的相关方法。

  • pthread_barrier_init障碍初始化
  • pthread_barrier_wait障碍等待
  • pthread_barrier_destroy障碍销毁

障碍同步(Barrier Synchronization)是Pthread中另外一种同步机制。
barrier意为栏杆,形象的说就是把先后到达的多个线程挡在同一栏杆前,直到所有线程到齐,然后撤下栏杆同时放行。

函数原型

int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
int pthread_barrier_wait(pthread_barrier_t *barrier);
int pthread_barrier_destroy(pthread_barrier_t *barrier);

pthread_barrier_init函数用于初始化一个屏障对象。它接受三个参数:

  1. barrier:指向要初始化的屏障对象的指针。
  2. attr:指向屏障属性对象的指针,可以为NULL。如果为NULL,则使用默认的属性。
  3. count:需要等待的线程数量。当所有线程都调用了pthread_barrier_wait函数后,屏障将被解除。

函数返回0表示成功,非0表示失败。

pthread_barrier_wait函数用于让当前线程等待其他线程到达屏障。它接受一个指向pthread_barrier_t类型的指针作为参数,该指针指向要等待的屏障对象。

当调用pthread_barrier_wait时,当前线程将被阻塞,直到所有其他线程都到达屏障。一旦所有线程都到达屏障,它们将继续执行并解除阻塞。

以下是一个示例代码,演示了如何使用pthread_barrier_wait函数:

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

// 定义线程数量和屏障对象
#define NUM_THREADS 5
pthread_barrier_t barrier;

// 线程函数
void* threadFunction(void* arg) {
 printf("Thread %ld is waiting on barrier\n", (long)arg);
 pthread_barrier_wait(&barrier);  // 等待其他线程到达屏障
 printf("Thread %ld has crossed the barrier\n", (long)arg);
 return NULL;
}

int main() {
 pthread_t threads[NUM_THREADS];

 // 初始化屏障对象
 pthread_barrier_init(&barrier, NULL, NUM_THREADS);

 // 创建并启动线程
 for (int i = 0; i < NUM_THREADS; i++) {
     pthread_create(&threads[i], NULL, threadFunction, (void*)(long)i);
 }

 // 等待所有线程完成
 for (int i = 0; i < NUM_THREADS; i++) {
     pthread_join(threads[i], NULL);
 }

 // 销毁屏障对象
 pthread_barrier_destroy(&barrier);

 return 0;
}

在上述示例中,我们创建了一个包含5个线程的线程池,并使用pthread_barrier_wait函数来同步这些线程。每个线程在到达屏障之前都会打印一条消息,表示正在等待其他线程。一旦所有线程都到达屏障,它们将打印另一条消息,表示已经跨越了屏障。最后,主线程等待所有子线程完成,然后销毁屏障对象。

int pthread_barrier_destroy(pthread_barrier_t *barrier); 是一个用于销毁线程屏障的函数。当一个线程屏障不再需要时,可以调用此函数来释放资源。

该函数接受一个指向 pthread_barrier_t 结构的指针作为参数,该结构表示要销毁的线程屏障。函数返回一个整数,表示操作的结果。如果成功销毁了线程屏障,则返回0;否则返回非零错误代码。

以下是一个示例代码,演示如何使用 pthread_barrier_destroy 函数:

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

// 定义线程数量和屏障对象
#define NUM_THREADS 5
pthread_barrier_t barrier;

void* threadFunction(void* arg) {
 printf("Thread %ld is waiting on barrier\n", (long)arg);
 pthread_barrier_wait(&barrier);  // 等待其他线程到达屏障
 printf("Thread %ld has crossed the barrier\n", (long)arg);
 return NULL;
}

int main() {
 pthread_t threads[NUM_THREADS];

 // 初始化线程并启动它们
 for (int i = 0; i < NUM_THREADS; i++) {
     pthread_create(&threads[i], NULL, threadFunction, (void*)(long)i);
 }

 // 等待所有线程完成
 for (int i = 0; i < NUM_THREADS; i++) {
     pthread_join(threads[i], NULL);
 }

 // 销毁线程屏障
 int result = pthread_barrier_destroy(&barrier);
 if (result == 0) {
     printf("Barrier destroyed successfully\n");
 } else {
     printf("Failed to destroy barrier\n");
 }

 return 0;
}

在上述示例中,我们创建了一个包含5个线程的线程池,每个线程都执行 threadFunction 函数。在函数中,线程使用 pthread_barrier_wait 函数等待其他线程到达屏障。一旦所有线程都到达屏障,它们将打印消息并继续执行。

在主函数中,我们首先启动所有线程,然后等待它们完成。最后,我们调用 pthread_barrier_destroy 函数来销毁线程屏障,并根据返回值判断是否成功销毁。

运行结果

Task1 will be blocked.
main process will sleep 1s.
Task2 will be blocked.
Task1 is running.
Task2 is running.

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

#define NUM_THREADS 2

pthread_barrier_t barrier;

void *Task1(void *arg);
void *Task2(void *arg);

int main(void)
{
    int policy, inher;
    pthread_t tid;
    pthread_attr_t attr;
    struct sched_param param;

    pthread_attr_init(&attr);
    pthread_barrier_init(&barrier, NULL, NUM_THREADS + 1);

    pthread_create(&tid, &attr, Task1, NULL);
    pthread_create(&tid, &attr, Task2, NULL);

    printf("main process will sleep 1s.\r\n");
    sleep(1);
    pthread_barrier_wait(&barrier);

    pthread_join(tid, NULL);
    pthread_barrier_destroy(&barrier);
}

void *Task1(void *arg)
{
    printf("Task1 will be blocked.\r\n");
    // your code here
    pthread_barrier_wait(&barrier);
    // end of your code
    printf("Task1 is running.\r\n");
    pthread_exit(NULL);
}

void *Task2(void *arg)
{
    printf("Task2 will be blocked.\r\n");
    // your code here
    pthread_barrier_wait(&barrier);
    // end of your code
    printf("Task2 is running.\r\n");
    pthread_exit(NULL);
}

获取调用线程标识号

本节展示了获取调用线程标识号的方法。

  • pthread_self获取调用线程标识号

函数说明

pthread_t pthread_self(void);
返回调用线程标识号,返回类型为pthread_t

pthread_t pthread_self(void); 是一个用于获取当前线程标识符的函数。在C语言中,可以使用POSIX线程库(pthread)来实现多线程编程。这个函数返回一个 pthread_t 类型的值,表示当前线程的唯一标识符。

运行结果

main thread: pid 1588 tid 2 (0x2)
new thread: pid 1588 tid 1 (0x1)

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

void *printids(void *arg)
{
    const char *str = (const char *)arg;
    pid_t pid;
    pthread_t tid;  
    pid = getpid();
	//your code here
    tid = pthread_self();
	//end of your code
    printf("%s pid %u tid %u (0x%x)\n", str, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);  
    return NULL; // 添加返回语句
}

int main()
{    
    pthread_t tid;
    int err;    
    err = pthread_create(&tid, NULL, printids, (void*)"new thread: ");
    if (err != 0) {
        fprintf(stderr, "can't create thread: %s\n", strerror(err));
        exit(1);
    }    
    printids((void*)"main thread: ");    
    sleep(1);
    return 0;
}

线程取消

pthread_cancel调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行。

函数说明

int pthread_cancel(pthread_t thread);

int pthread_cancel(pthread_t thread); 用于请求取消指定线程。它接受一个 pthread_t 类型的参数,表示要取消的线程标识符。如果成功取消线程,函数返回0;否则返回错误码。这个函数允许主线程或者其他线程发起对目标线程的取消请求。

参数说明:

  • pthread_t thread:要取消的线程标识符,即你想结束的那个线程的句柄。

返回值:

  • 如果成功发送了取消请求,则返回0。
  • 如果发生错误(例如,无效的线程ID),则返回非零错误代码。

注意:

  1. 取消一个线程并不意味着它会立即停止执行。目标线程必须检查并处理取消请求才能真正终止。通常情况下,线程需要在安全点检查pthread_cancel状态,并调用pthread_testcancel来响应取消请求。

  2. 线程可以通过设置取消类型为PTHREAD_CANCEL_DISABLE来暂时禁用取消,或者通过调用pthread_setcancelstate函数改变其取消状态。

  3. 为了正确地清理资源,线程可以注册一个取消处理函数(pthread_cleanup_pushpthread_cleanup_pop),当线程被取消时,这些函数会被自动调用。

  4. 默认情况下,线程的取消类型是PTHREAD_CANCEL_DEFERRED,这意味着只有在线程进入取消点时才会真正取消。若要使取消立即生效,可以将线程的取消类型设置为PTHREAD_CANCEL_ASYNCHRONOUS

示例:

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

void* thread_function(void* arg) {
    // 在这里进行线程任务...
    while(1) {
        // 定期检查取消状态
        pthread_testcancel();
        // 这里做一些工作...
    }
    return NULL;
}

int main() {
    pthread_t thread_id;
    int rc;

    // 创建线程
    rc = pthread_create(&thread_id, NULL, thread_function, NULL);
    if (rc) {
        printf("Error: Unable to create thread, %d\n", rc);
        exit(EXIT_FAILURE);
    }

    // 在某个时刻决定取消线程
    sleep(5); // 假设等待一段时间后决定取消线程
    rc = pthread_cancel(thread_id);
    if (rc) {
        printf("Error: Unable to cancel thread, %d\n", rc);
    } else {
        printf("Thread cancellation requested.\n");
    }

    // 等待被取消的线程终止
    pthread_join(thread_id, NULL);

    return 0;
}

运行结果

为了比较输出结果,请将环境设置中的并行核数调为1。本实验暂不支持性能评测。

thread 1 returning
thread 1 exit code 1
thread 2 exiting
thread 2 exit code 2
thread 3 writing
thread 3 writing
thread 3 writing
thread 3 exit code -1

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

void *thr_fn1(void *arg)
{
    printf("thread 1 returning\n");
    return (void*)1;
}

void *thr_fn2(void *arg)
{
    printf("thread 2 exiting\n");
    pthread_exit((void*)2);
}

void *thr_fn3(void *arg)
{
    int time = 0;
    while(1) {
        time++;
        printf("thread 3 writing\n");
        sleep(1);
        if(time >= 4) return NULL;
    }
}

int main()
{
    pthread_t tid;
    void *retval;

    pthread_create(&tid, NULL, thr_fn1, NULL);
    pthread_join(tid, &retval);
    printf("thread 1 exit code %d\n", (int)(intptr_t)retval); // 修复类型转换

    pthread_create(&tid, NULL, thr_fn2, NULL);
    pthread_join(tid, &retval);
    printf("thread 2 exit code %d\n", (int)(intptr_t)retval); // 修复类型转换

    pthread_create(&tid, NULL, thr_fn3, NULL);
    sleep(2);

    //your code here
    pthread_cancel(tid);
    
    //end of your code
    pthread_join(tid, &retval); // 需要处理取消后的线程可能没有有效退出状态的情况
    if(retval == PTHREAD_CANCELED) {
        printf("thread 3 was canceled\n");
    } else {
        printf("thread 3 exit code %d\n", (int)(intptr_t)retval); // 这里仍需进行正确的类型转换
    }

    return 0;
}

线程属性 - 线程连接和分离(2)

本节展示了线程连接和分离的基本知识。

基本知识

  • pthread_detach将某个线程设成分离态

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL。如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

通常情况下,若创建一个线程不关心它的返回值,也不想使用pthread_join来回收资源(调用pthread_join的进程会阻塞),就可以使用pthread_detach,将该线程的状态设置为分离态,使线程结束后,立即被系统回收。
主线程退出了,“分离线程”还是一样退出。只是“分离线程”的资源是有系统回收的。

函数说明

将某个线程设成分离态

int pthread_detach(pthread_t tid);

pthread_t tid:  分离线程的tid
返回值:成功返回0,失败返回错误号。

pthread_detach是POSIX线程库中的一个函数,用于将一个已创建的线程从其创建者分离(detach)。

函数原型:

int pthread_detach(pthread_t tid);

参数解释:

  • pthread_t tid:这是要分离的线程的标识符。在创建线程时,pthread_create函数会返回这个tid,代表新创建的线程。

函数功能:
当一个线程被分离后,其资源(如栈空间等)将在该线程终止时自动释放,而无需调用pthread_join函数等待其完成。这意味着分离线程执行完毕后,不会保留joinable状态,因此主线程或其他线程无法通过pthread_join来获取其返回值或确保它已经结束。

返回值:

  • 若成功分离线程,则返回0。
  • 若出错,则返回非零错误编号,具体错误信息可通过errno获取。

分离线程的主要用途是在不关心线程退出状态和返回值的情况下,减轻主线程对子线程生命周期管理的负担。

输出结果

thread1 running...!
thread1 running...!
thread1 running...!
thread1 running...!
thread1 running...!
thread1 running...!
thread1 running...!
thread1 running...!
thread1 running...!
Leave main thread!

声明

由于并行程序执行顺序的不确定性,你的结果可能和这个结果不一致。

在给出的代码中,thread1函数是一个无限循环的线程,因此实际上调用pthread_detach之后主线程无法得知thread1何时结束。这种情况下,分离线程的意义在于主线程结束后,即使未调用pthread_join,系统也会自动释放thread1所占用的资源。

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

// 定义线程执行的函数
void* thread1(void *arg)
{ 
    while (1) // 这是一个无限循环的线程
    {
        usleep(100 * 1000); // 暂停100毫秒
        printf("thread1 running...!\n");
    }
    // 实际上由于是无限循环,下面这行代码可能永远不会被执行
    printf("Leave thread1!\n"); 
    return NULL;
} 

int main(int argc, char** argv)
{
    pthread_t tid; // 创建线程标识符

    // 创建并启动线程
    if(pthread_create(&tid, NULL, thread1, NULL) != 0) {
        perror("pthread_create failed");
        exit(EXIT_FAILURE);
    }

    // 分离线程,使其在完成后自动释放资源,无需等待主线程调用pthread_join
    if(pthread_detach(tid) != 0) {
        perror("pthread_detach failed");
        exit(EXIT_FAILURE);
    }

    sleep(1); // 主线程暂停1秒
    printf("Leave main thread! (The detached thread 'thread1' will continue to run in the background.)\n"); 

    return 0;
}

这段代码中,增加了对pthread_createpthread_detach错误处理的部分,使得程序在创建或分离线程失败时能够打印错误信息并退出。同时,主线程输出信息也说明了detach线程后的情况。

比较线程标识号

本节介绍了比较两个线程的线程标识符是否相同的方法。

线程标识号的类型为pthread_t,是不透明类型。为了使程序更容易移植,Pthreads提供了对两个线程的线程标识号进行比较的函数。

函数说明

比较两个线程的线程标识符是否相同

pthread_equal用于比较两个线程的标识符是否相同

int pthread_equal(pthread_t t1, pthread_t t2);

参数解释:

  • pthread_t t1:第一个线程标识符。
  • pthread_t t2:第二个线程标识符。

该函数比较t1t2两个线程标识符是否指向同一线程。如果两者指向同一个线程,则返回非零值(通常为1),否则返回0。

使用场景:
在多线程编程中,有时需要确定两个线程标识符是否对应于同一个线程实例。例如,在同步或通信操作中,可能需要检查当前线程是否与某个特定的已知线程相匹配。

示例代码:

pthread_t tid1, tid2;
// 假设tid1和tid2是从pthread_create得到的不同线程的标识符
if (pthread_create(&tid1, NULL, thread_function1, NULL) == 0) {
    // 创建线程成功
}

if (pthread_create(&tid2, NULL, thread_function2, NULL) == 0) {
    // 创建线程成功
}

// 检查tid1和tid2是否代表同一个线程
if (pthread_equal(tid1, tid2)) {
    printf("tid1 and tid2 represent the same thread.\n");
} else {
    printf("tid1 and tid2 represent different threads.\n");
}

在这个例子中,pthread_equal(tid1, tid2)将返回0,因为它们分别代表两个不同线程。

运行结果

Thread ID: 1.
Equal!
#include <stdio.h>
#include <pthread.h>

int main(){
    pthread_t thread_id;

    // 获取当前线程ID
    thread_id = pthread_self();
    printf("Thread ID: %lu.\n", thread_id);

    // 检查两次获取的线程ID是否相同(理论上它们应该总是相同的)
    int t = pthread_equal(thread_id, pthread_self());

    if (t)  
    {  
        printf("The Thread IDs are equal, as expected!\n");  
    } else {  
        printf("Unexpected: The Thread IDs are not equal!\n");
    }  

    return 0;
}

线程私有数据

本节展示了线程私有数据的相关方法。

pthread_key_createpthread_setspecificpthread_getspecificpthread_key_delete这一系列函数主要用于管理线程特定的数据(Thread-Specific Data, TSD),也称为线程局部存储(Thread-Local Storage, TLS)

  • pthread_key_create创建一个键
  • pthread_setspecific为一个键设置线程私有数据
  • pthread_getspecific从一个键读取线程私有数据
  • pthread_key_delete删除一个键

在多线程的环境下,进程内的所有线程共享进程的数据空间。因此全局变量为所有线程共享。在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在线程内部有效。
线程私有数据采用了一键多值的技术,即一个键对应多个值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。

工作流程

1、创建一个键
2、为一个键设置线程私有数据
3、从一个键读取线程私有数据void *pthread_getspecific(pthread_key_t key);
4、线程退出(退出时,会调用destructor释放分配的缓存,参数是key所关联的数据)
5、删除一个键

函数说明

创建一个键

int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

pthread_key_create(pthread_key_t *key, void (*destructor)(void*))c

  • 第一个参数为指向一个键值的指针,第二个参数指明了一个destructor函数,
    如果第二个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
    key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量,一键多值。
  • 创建一个线程私有数据键。参数key是一个指向pthread_key_t类型的指针,用于存储创建的键值;destructor是一个可选的清理函数,当关联此键的线程终止时,该函数会被调用来释放分配给这个键的私有数据。

为一个键设置线程私有数据

int pthread_setspecific(pthread_key_t key,const void *pointer));

pthread_setspecific(pthread_key_t key, const void *value)

  • 为指定的键设置线程私有数据。每个线程都可以为同一个键设置不同的值,互不影响。参数key是通过pthread_key_create创建的键,value是要关联到此键的私有数据指针。

从一个键读取线程私有数据

void *pthread_getspecific(pthread_key_t key);

void* pthread_getspecific(pthread_key_t key)

  • 根据指定的键获取当前线程关联的私有数据。返回与键关联的线程特定数据的指针。

删除一个键

int pthread_key_delete(pthread_key_t key);
删除后,键所占用的内存将被释放。

int pthread_key_delete(pthread_key_t key)

  • 删除已创建的线程私有数据键。一旦键被删除,所有线程与该键关联的私有数据都将失效,如果之前设置了清理函数,会在最后一个与该键关联的线程结束时执行。

举例说明:

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

static pthread_key_t thread_data_key;

void cleanup_thread_data(void* data) {
    free(data);
}

void* thread_function(void* arg) {
    int* data = malloc(sizeof(int));
    *data = 42; // 初始化私有数据

    pthread_setspecific(thread_data_key, data);

    // 在线程执行过程中可以随时通过键访问自己的私有数据
    int* my_data = pthread_getspecific(thread_data_key);
    printf("Thread private data: %d\n", *my_data);

    return NULL;
}

int main() {
    if (pthread_key_create(&thread_data_key, cleanup_thread_data) != 0) {
        perror("pthread_key_create");
        exit(EXIT_FAILURE);
    }

    pthread_t tid;
    pthread_create(&tid, NULL, thread_function, NULL);

    // 等待子线程结束
    pthread_join(tid, NULL);

    // 主线程也可以有自己的私有数据,与子线程互不影响
    // ...

    // 最后删除键
    pthread_key_delete(thread_data_key);

    return 0;
}

这段代码展示了如何创建、设置、获取以及删除线程私有数据。每个线程通过pthread_setspecific保存自己的私有数据,并且可以在任何时候通过pthread_getspecific来访问这些数据,而不会影响其他线程。当线程结束时,若定义了清理函数,会自动清理其私有数据。

运行结果

main thread:1 is running
thread:2 is running
thread:2 return 1
thread:3 is running
thread:3 return 2
thread:1 return 0
main thread exit
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

pthread_key_t key1;
pthread_key_t key2;
pthread_t thid1;
pthread_t thid2;

void* thread1(void* arg);
void* thread2(void* arg);

void* thread1(void* arg)
{
    printf("thread:%lu is running\n", pthread_self());
    int key_va = 1;
    pthread_setspecific(key1, (void*)&key_va);
    pthread_create(&thid2, NULL, thread2, NULL);
    pthread_join(thid2, NULL); // 添加了对线程2的等待,确保其执行完毕后再返回
    printf("thread:%lu return %d\n", pthread_self(), *(int*)pthread_getspecific(key1));
    return NULL;
}

void* thread2(void* arg)
{
    printf("thread:%lu is running\n", pthread_self());
    int key_va = 2;
    pthread_setspecific(key2, (void*)&key_va);
    printf("thread:%lu return %d\n", pthread_self(), *(int*)pthread_getspecific(key2));
    return NULL; // 添加了返回语句以消除编译警告
}

int main()
{
    printf("main thread:%lu is running\n", pthread_self());
    pthread_key_create(&key1, NULL);
    pthread_key_create(&key2, NULL);
    pthread_create(&thid1, NULL, thread1, NULL);
    
    pthread_join(thid1, NULL);

    pthread_key_delete(key1);
    pthread_key_delete(key2);
    printf("main thread exit\n");
    return 0;
}

读写锁

本节展示了读写锁的相关方法。

基本知识

读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程对其加锁。
读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可用同时占有读模式的读写锁。
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的。

  1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞;
  2. 当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞;
  3. 当读写锁在读模式锁状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞;
  • pthread_rwlock_init创建锁

  • pthread_rwlock_destroy销毁锁

  • pthread_rwlock_rdlock获取一个读出锁

  • pthread_rwlock_wrlock获取一个写入锁

  • pthread_rwlock_unlock释放一个写入锁或者读出锁

  • pthread_rwlock_tryrdlock非阻塞获得读锁

  • pthread_rwlock_trywrlock非阻塞获得写锁

在多线程编程中,读写锁(也称为读-写锁或共享-独占锁)是一种能够提供更细粒度控制的同步机制。它允许一个资源在同一时间被多个读者线程同时读取,但在任何时候只允许一个写者线程进行写入操作。这样可以提高系统并发性能,特别是在读多写少的情况下。

以下是使用读写锁的基本操作示例:

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

pthread_rwlock_t rwlock;

void initialize_rwlock() {
    pthread_rwlock_init(&rwlock, NULL);
}

void read_lock_and_operation() {
    pthread_rwlock_rdlock(&rwlock);
    // 在这里执行读取操作
    printf("Reading from a shared resource by thread %lu.\n", pthread_self());
    pthread_rwlock_unlock(&rwlock);
}

void write_lock_and_operation() {
    pthread_rwlock_wrlock(&rwlock);
    // 在这里执行写入操作
    printf("Writing to the shared resource by thread %lu.\n", pthread_self());
    // 更新共享资源...
    pthread_rwlock_unlock(&rwlock);
}

void destroy_rwlock() {
    pthread_rwlock_destroy(&rwlock);
}

int main() {
    initialize_rwlock();

    // 创建并启动多个读线程和写线程

    // 例如:
    pthread_t reader_thread;
    pthread_create(&reader_thread, NULL, (void*)read_lock_and_operation, NULL);

    pthread_t writer_thread;
    pthread_create(&writer_thread, NULL, (void*)write_lock_and_operation, NULL);

    pthread_join(reader_thread, NULL);
    pthread_join(writer_thread, NULL);

    destroy_rwlock();

    return 0;
}

另外,pthread_rwlock_tryrdlockpthread_rwlock_trywrlock 函数提供了非阻塞版本的读写锁获取操作,如果无法立即获取锁,则返回错误而非阻塞等待:

if (pthread_rwlock_tryrdlock(&rwlock) == 0) {
    // 成功获取读锁,执行读取操作
} else {
    // 锁不可用,采取相应策略(如:稍后重试、切换至其他任务等)
}

if (pthread_rwlock_trywrlock(&rwlock) == 0) {
    // 成功获取写锁,执行写入操作
} else {
    // 锁不可用,采取相应策略
}

通过合理利用读写锁,可以有效避免不必要的线程阻塞,从而提升程序的并发性和整体性能。

创建与销毁锁

pthread_rwlock_initpthread_rwlock_destroy是POSIX线程库中的读写锁初始化和销毁函数。

  1. pthread_rwlock_init

    int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
    • 功能:初始化一个读写锁变量。这个函数用于创建一个新的读写锁,使它可以被多个线程用来同步对共享资源的读写访问。
    • 参数:
      • pthread_rwlock_t *rwlock:指向读写锁对象的指针,需要先声明并初始化为NULL。
      • const pthread_rwlockattr_t *attr:指向读写锁属性对象的指针,可以设置锁的一些高级特性(如进程间共享等)。如果不需要特殊属性,通常传入NULL即可使用默认属性。
  2. pthread_rwlock_destroy

    int pthread_rwlock_destroy(pthread_rwlock_t *rwptr);
    • 功能:销毁已经初始化过的读写锁。在不再需要使用读写锁,并且所有线程都已解锁的情况下,调用此函数来释放与该锁关联的系统资源。
    • 参数:
      • pthread_rwlock_t *rwptr:指向之前通过pthread_rwlock_init成功初始化过的读写锁对象的指针。

正确使用这两个函数的方式如下:

pthread_rwlock_t my_rwlock;

// 初始化读写锁
if (pthread_rwlock_init(&my_rwlock, NULL) != 0) {
    perror("pthread_rwlock_init failed");
    exit(EXIT_FAILURE);
}

// 使用读写锁进行同步...
// ...

// 在不再需要读写锁时销毁它
if (pthread_rwlock_destroy(&my_rwlock) != 0) {
    perror("pthread_rwlock_destroy failed");
    // 处理错误或选择忽略(取决于程序逻辑)
}

注意:在销毁读写锁前,必须确保没有任何线程正持有该锁(无论是读锁还是写锁),否则pthread_rwlock_destroy会失败并返回错误。

获取和释放锁

  1. pthread_rwlock_rdlock

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr);
    • 功能:对给定的读写锁(rwptr)进行读取锁定。当调用此函数时,如果当前没有其他线程持有写入锁,并且没有正在等待获取写入锁的线程,则当前线程可以获得读取锁并开始读取共享资源。如果有写入锁被持有或有线程在等待写入锁,那么该函数会阻塞直到可以获取读取锁。
    • 返回值:成功返回0,出错返回非零错误代码。
  2. pthread_rwlock_wrlock

    int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr);
    • 功能:对给定的读写锁(rwptr)进行写入锁定。当调用此函数时,无论是否有其他线程持有读取锁或者写入锁,都会阻塞直到当前线程能够获得写入锁。只有在没有任何线程持有读取锁或写入锁的情况下,当前线程才能获取写入锁并开始修改共享资源。
    • 返回值:成功返回0,出错返回非零错误代码。
  3. pthread_rwlock_unlock

    int pthread_rwlock_unlock(pthread_rwlock_t *rwptr);
    • 功能:释放一个之前通过pthread_rwlock_rdlockpthread_rwlock_wrlock获取的读出锁或写入锁。调用此函数后,其他等待相同锁的线程(无论是读取还是写入请求)可能会被唤醒并有机会获取锁。
    • 返回值:成功返回0,出错返回非零错误代码。

示例使用:

pthread_rwlock_t rwlock;
int shared_data;

void reader() {
    pthread_rwlock_rdlock(&rwlock);
    // 读取shared_data
    printf("Reader: Data is %d\n", shared_data);
    pthread_rwlock_unlock(&rwlock);
}

void writer(int new_value) {
    pthread_rwlock_wrlock(&rwlock);
    // 修改shared_data
    shared_data = new_value;
    printf("Writer: Updated data to %d\n", shared_data);
    pthread_rwlock_unlock(&rwlock);
}

int main() {
    pthread_rwlock_init(&rwlock, NULL);

    // 创建和启动多个读线程与写线程

    // 销毁读写锁
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

非阻塞获得读锁和写锁

  1. pthread_rwlock_tryrdlock

    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    • 功能:尝试获取给定的读写锁(rwlock)的读取锁,但不阻塞。如果当前没有其他线程持有写入锁,并且没有正在等待获取写入锁的线程,则立即返回并成功获取读取锁。否则,如果无法立即获取读取锁,则该函数会立即返回一个错误值。
    • 返回值:若成功获取读取锁则返回0,若无法立即获取锁则返回一个非零错误代码(如EBUSY),表示资源已经被锁定。
  2. pthread_rwlock_trywrlock

    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    • 功能:尝试获取给定的读写锁(rwlock)的写入锁,同样不会阻塞。只有在没有任何线程持有读取锁或写入锁的情况下,该函数才能立即返回并成功获取写入锁。否则,如果无法立即获取写入锁,则该函数会立即返回一个错误值。
    • 返回值:若成功获取写入锁则返回0,若无法立即获取锁则返回一个非零错误代码(如EBUSY),表示资源已经被锁定。

使用这两个函数可以在需要避免阻塞时尝试获取锁,例如在实时系统或者不能无限期等待锁的地方:

pthread_rwlock_t rwlock;

void non_blocking_reader() {
    if (pthread_rwlock_tryrdlock(&rwlock) == 0) {
        // 成功获取读锁,执行读取操作
        printf("Reader: Data is %d\n", shared_data);
        pthread_rwlock_unlock(&rwlock);
    } else {
        printf("Reader: Could not acquire read lock, data not read.\n");
        // 执行其他操作或稍后重试
    }
}

void non_blocking_writer(int new_value) {
    if (pthread_rwlock_trywrlock(&rwlock) == 0) {
        // 成功获取写锁,执行写入操作
        shared_data = new_value;
        printf("Writer: Updated data to %d\n", shared_data);
        pthread_rwlock_unlock(&rwlock);
    } else {
        printf("Writer: Could not acquire write lock, data not updated.\n");
        // 执行其他操作或稍后重试
    }
}

运行结果

reader 1 is reading data.
reader 2 is reading data.
reader 3 is reading data.
writer 5 is writing data.
writer 4 is writing data.
reader 1 reads data over.
reader 2 reads data over.
reader 3 reads data over.
writer 5 writes data over.
writer 4 writes data over.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

#define        READER_MAX    3        /*最多允许多少人一起读取数据*/
#define        WRITER_MAX    2        /*最多允许多少人写数据*/

pthread_rwlock_t    rw_lock;        /*读写锁*/

void* reader_thread(void *arg)
{
    while(1) {
		int t;
		//your code here
		t = pthread_rwlock_tryrdlock(&rw_lock);
		//end of your code
        if(t) {
            printf("reader %u can't read data.\n", (unsigned int)pthread_self());
            sleep(1);
        } else {
            printf("reader %u is reading data.\n", (unsigned int)pthread_self());
            sleep(1);
            printf("reader %u reads data over.\n", (unsigned int)pthread_self());
            pthread_rwlock_unlock(&rw_lock); 
            sleep(2);   
        }    
    }
}

void* writer_thread(void *arg)
{
    while(1) {
		int t;
		//your code here
		t = pthread_rwlock_tryrdlock(&rw_lock);
		//end of your code
        if(t) {
            printf("writer %u can't write data.\n", (unsigned int)pthread_self());
            sleep(2);
        } else {
            printf("writer %u is writing data.\n", (unsigned int)pthread_self());
            sleep(2); 
            printf("writer %u writes data over.\n", (unsigned int)pthread_self());
            pthread_rwlock_unlock(&rw_lock);
            sleep(3); 
        }
    }
}

int main(int argc, char* argv[])
{
    pthread_t reader, writer;
    int i = 0; 
    pthread_rwlock_init(&rw_lock, NULL);
    for(i = 0; i < READER_MAX; i++)    
        pthread_create(&reader, NULL, reader_thread, NULL);
    for(i = 0; i < WRITER_MAX; i++)
        pthread_create(&writer, NULL, writer_thread, NULL);
    sleep(3);
    
    return 0;
}

一次初始化

  • pthread_once一次初始化

在多线程环境中,有些事仅需要执行一次。通常当初始化应用程序时,可以比较容易地将其放在main函数中。但当你写一个库时,就不能在main里面初始化了,你可以用静态初始化,但使用一次初始化(pthread_once)会比较容易些。

在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。

pthread_once是POSIX线程库中的一个函数,用于确保某个初始化函数在整个程序生命周期内只被执行一次。这对于那些需要单次初始化且初始化过程必须在多线程环境下安全执行的情况非常有用。

函数原型:

int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

参数解析:

  • pthread_once_t *once_control:指向一个pthread_once_t类型的变量的指针。这个变量是用来跟踪初始化是否已经完成的状态标志。
  • void (*init_routine)(void):这是一个指向无参数、无返回值函数的指针,即初始化函数。当pthread_once检测到初始化尚未执行时,它将调用这个函数进行初始化。

功能描述:
pthread_once函数会检查once_control指向的控制变量状态,如果初始化尚未执行,则调用init_routine函数进行初始化,并设置控制变量为已初始化状态。此函数保证了在多线程环境中即使多个线程同时调用pthread_once,初始化函数也只会被执行一次。

示例使用:

#include <pthread.h>

static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static int shared_resource;

static void initialize_shared_resource() {
    // 这里进行一次性初始化操作
    shared_resource = calculate_initial_value();
}

void access_shared_resource() {
    pthread_once(&once_control, initialize_shared_resource);
    // 现在可以安全地访问shared_resource了
    use_shared_resource(shared_resource);
}

在这个例子中,initialize_shared_resource函数只有在第一次调用access_shared_resource函数并传递给pthread_once时才会被执行。后续对access_shared_resource的调用将直接跳过初始化阶段,因为once_control已经被标记为已初始化状态。

运行结果

thread 1 enters
thread 2 enters
once_run in thread 1
thread 1 returns
thread 2 returns
#include <stdio.h>  
#include <pthread.h>  
#include <unistd.h> 
#include <stdlib.h>

pthread_once_t once = PTHREAD_ONCE_INIT;  

void once_run(void)  
{  
	printf("once_run in thread %u\n", (unsigned int)pthread_self());
}  

void * child1(void * arg)  
{  
    pthread_t tid = pthread_self();  
    printf("thread %u enters\n", (unsigned int)tid);
	//your code here
    pthread_once(&once,once_run);  
	//end of your code
    printf("thread %u returns\n", (unsigned int)tid);
    return NULL;
}  


void * child2(void * arg)  
{  
    pthread_t tid = pthread_self();  
    printf("thread %u enters\n", (unsigned int)tid);
	//your code here
    pthread_once(&once,once_run);  
	//end of your code
    printf("thread %u returns\n", (unsigned int)tid);
    return NULL;
}  

int main()  
{  
    pthread_t tid1,tid2;  
    pthread_create(&tid1,NULL,child1,NULL);  
    pthread_create(&tid2,NULL,child2,NULL);  
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);
    return 0;  
}  

发送sig信号

本节展示了发送sig信号的方法。

基本知识

  • pthread_kill发送sig信号

pthread_kill并不是结束进程的意思,而是向指定ID的线程发送一个信号,只是大部分信号的默认动作是终止进程的运行。
如果线程不处理该信号,则按照信号默认的行为作用于整个进程。信号值0为保留信号,作用是根据函数的返回值判断线程是不是还活着。
pthread_kill的返回值:成功(0) 线程不存在(ESRCH) 信号不合法(EINVAL)

函数说明

pthread_kill,用于向指定的线程发送信号。

int pthread_kill(pthread_t thread, int sig);

参数解析:

  • pthread_t thread:这是目标线程的标识符,可以通过调用pthread_create函数创建线程时返回的值获取。
  • int sig:要发送给线程的信号。这个参数可以是POSIX定义的任何有效信号(例如SIGTERM、SIGINT等),或者自定义信号(如1~31之间的数字)。

功能描述:
pthread_kill函数允许主线程或者其他线程向指定的线程发送一个信号。当目标线程接收到该信号后,它会根据预先注册的信号处理函数(通过signalsigaction设置)来处理这个信号,或者是默认的行为(比如对于SIGTERM,通常会导致进程/线程终止)。

示例使用:

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

void signal_handler(int signum) {
    printf("Signal %d received in the thread\n", signum);
}

void* thread_function(void* arg) {
    signal(SIGUSR1, signal_handler); // 注册信号处理器
    while(1) {
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    if(pthread_create(&tid, NULL, thread_function, NULL)) {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }

    sleep(5); // 等待线程开始运行

    if(pthread_kill(tid, SIGUSR1)) { // 向线程发送SIGUSR1信号
        perror("pthread_kill");
    }

    pthread_join(tid, NULL); // 等待线程结束
    return 0;
}

在这个例子中,我们创建了一个新的线程,并在其中注册了SIGUSR1信号的处理函数。然后,在主线程中使用pthread_kill向子线程发送SIGUSR1信号,触发其内部的信号处理函数执行。

运行结果

ID 0x1 thread exits.
ID 0x1 thread exits.
ID 0x2 thread stays alive now.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h> 
#include <signal.h>

void *func1(void* args)
{
    sleep(1);
    printf("ID 0x%x thread exits.\n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void *func2(void* args)
{
    sleep(3);
    printf("ID 0x%x thread exits。\n",(unsigned int)pthread_self());
    pthread_exit((void *)0);
}

void test_pthread(pthread_t tid) 
{
    int pthread_kill_err;
	//your code here
    pthread_kill_err = pthread_kill(tid,0);
	//end of your code

    if(pthread_kill_err == ESRCH)
        printf("ID 0x%x thread exits.\n",(unsigned int)tid);
    else if(pthread_kill_err == EINVAL)
        printf("invalid signal\n");
    else
        printf("ID 0x%x thread stays alive now.\n",(unsigned int)tid);
}

int main()
{
    int ret;
    pthread_t tid1,tid2;
    
    pthread_create(&tid1,NULL,func1,NULL);
    pthread_create(&tid2,NULL,func2,NULL);
    
    sleep(2);
    
    test_pthread(tid1);
    test_pthread(tid2);

    exit(0);
}

文章作者: loyeh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 loyeh !
评论
  目录