线程

发布时间 2023-10-07 09:28:36作者: ⭐⭐-fighting⭐⭐

线程

生产者与消费者模型

Linux的线程实现

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。Linux下pthread是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似于fork()。

pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数解释:

  1. pthread_t *thread:线程ID。这是一个指针,指向一个pthread_t类型的变量,该变量将被设置为新创建线程的线程ID。线程ID在创建线程后用于识别和管理线程。
  2. const pthread_attr_t *attr:线程属性。这是一个指向pthread_attr_t类型结构的指针,用于设置线程的属性。如果设置为NULL,则使用默认属性(例如,线程将具有与创建它的父线程相同的堆栈大小、调度优先级等)。可以使用pthread_attr_init()函数初始化线程属性结构,并使用其他pthread_attr_*()函数设置各种属性,例如堆栈大小、调度策略等。当不再需要该属性时,可以使用pthread_attr_destroy()函数销毁线程属性结构。
  3. void *(*start_routine) (void *):线程执行函数。这是一个指向函数的指针,新创建的线程将从该函数开始执行。这个函数必须返回void *类型,并接受一个void *类型的参数。通常情况下,这个函数是由用户自己定义的,包含了线程要执行的任务和逻辑。
  4. void *arg:线程执行函数的参数。这是传递给start_routinevoid *类型的参数。可以将任意类型的指针转换为void *类型,并在start_routine中进行适当的类型转换。这允许您将任意数据传递给线程执行函数。

返回值:pthread_create函数返回一个整数,表示创建线程的成功或失败状态。如果成功创建线程,函数返回0;如果创建失败,返回一个非零错误代码。

因为pthread不是Linux系统的库,所以在进行编译时要加上-lpthread,例如:

# gcc filename -lpthread

示例:

在代码中获得当前线程标识符的函数为:pthread_self()。

#include <stdio.h>
#include <pthread.h>
// int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *func1(void *arg)
{
	printf("t1:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t1:param is %d\n", *((int *)arg));
}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;

	ret = pthread_create(&t1, NULL, func1, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t1 success\n");
	}
	printf("main:%ld\n", (unsigned long)pthread_self());
	pthread_join(t1, NULL);
	return 0;
}

image

假设我们有一个计算平方和的任务,并希望在新线程中执行。可以这样定义线程执行函数:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *compute_square_sum(void *arg)
{
    int *num = (int *)arg;
    int result = (*num) * (*num);
    printf("Squaresum of %d is %d\n", *num, result);
    pthread_exit((void *)&result);
}
int main()
{
    pthread_t thread_id;
    int num = 5;
    int ret = pthread_create(&thread_id, NULL, compute_square_sum, &num);
    if (ret != 0)
    {
        printf("Error creating thread\n");
        exit(1);
    }
    // 等待新线程执行完毕
    void *result;
    pthread_join(thread_id, &result);
    printf("Thread exited with result %d\n", *(int *)result);
    return 0;
}

image

这个例子中,pthread_create函数将创建一个新线程,并将compute_square_sum函数作为新线程的执行函数,将指向整数变量num的指针作为参数传递给该函数。在compute_square_sum函数中,我们将arg指针转换为int *类型,并计算num的平方和。最后,我们使用pthread_exit函数来退出线程,并返回指向结果的指针。

在主线程中,我们使用pthread_join函数来等待新线程完成,并检索compute_square_sum函数的结果。pthread_join将阻塞主线程,直到新线程执行完毕,并将结果存储在result指针中。

当新线程执行完毕时,它将调用pthread_exit函数,将结果作为指针传递给主线程。在主线程中,我们将result指针转换为int *类型,并打印计算结果。

总的来说,pthread_create函数是创建新线程的关键函数之一。它允许您指定线程的各种属性,并为线程提供执行函数和参数。使用pthread_create函数,可以轻松地创建并发应用程序,以利用多核处理器和分布式系统的性能优势。

在使用pthread_create函数时,有几点需要注意:

  1. 由于线程共享进程中的同一地址空间,因此在线程之间共享数据时需要特别小心。在多个线程中同时访问同一数据时,可能会发生竞态条件和死锁等问题。您需要使用同步机制(例如互斥锁、条件变量等)来保护共享数据的访问。
  2. 在使用pthread_create函数创建多个线程时,您需要小心系统资源的使用情况。每个线程都需要一定的堆栈空间和其他资源,过多的线程可能会导致系统资源耗尽,从而影响整个系统的性能。
  3. 在使用pthread_create函数创建线程时,需要注意线程的生命周期。如果您创建了一个新线程但没有正确地等待其完成并释放其资源,可能会导致内存泄漏或其他问题。您应该使用pthread_join函数来等待线程完成,并使用pthread_exit函数来退出线程并释放其资源。
  4. 在使用pthread_create函数时,需要小心线程的优先级设置。线程的优先级决定了线程在调度时的相对顺序。如果您设置了多个线程的优先级相同,可能会导致线程饥饿和调度问题。您应该使用pthread_attr_setschedpolicypthread_attr_setschedparam等函数来设置线程的调度策略和优先级,并确保线程得到适当的调度和资源分配。
  5. 在使用pthread_create函数时,需要注意线程的并发访问问题。如果多个线程同时访问同一数据结构或共享资源,可能会导致数据不一致或其他问题。您应该使用同步机制(例如互斥锁、信号量、条件变量等)来保护共享数据的访问,并确保线程之间的同步和协调。
  6. 在使用pthread_create函数时,需要注意线程的可移植性问题。不同的操作系统和平台可能有不同的线程实现和特性,因此您应该使用标准的POSIX线程库函数,并避免使用非标准的扩展功能。

总的来说,pthread_create函数是一个非常强大和灵活的函数,可以用于创建并发应用程序和多线程服务器等。使用正确的线程创建和管理技术,可以充分利用现代计算机的性能,并实现高效、可伸缩、可靠的应用程序。

pthread_join

pthread_join函数是POSIX线程库中的一个函数,它用于等待一个线程完成。它的原型如下:

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

参数解释:

  1. pthread_t thread:线程ID。这是要等待的线程的ID。
  2. void **retval:线程的返回值。这是一个指向指针的指针,用于存储线程的返回值。如果线程没有返回值,则可以将该参数设置为NULL

返回值:pthread_join函数返回一个整数,表示等待线程的成功或失败状态。如果成功等待线程完成,函数返回0;如果等待失败,返回一个非零错误代码。

使用 pthread_join() 函数时需要注意以下几点:

  1. 如果线程尚未终止,则调用 pthread_join() 函数将一直阻塞,直到指定线程终止为止。
  2. 如果线程已经终止,则 pthread_join() 函数将立即返回,并将线程的返回值存储在 value_ptr 指向的内存中。
  3. 如果线程已经被分离成为多个线程,则只需要分别调用 pthread_join() 函数等待每个线程的终止即可。
  4. 如果在线程函数中使用了 pthread_exit() 函数来终止线程,则线程的返回值将被设置为 pthread_exit() 函数的参数。如果没有使用 pthread_exit() 函数,则线程的返回值将默认为 0。
  5. 需要注意的是,pthread_join() 函数只能等待一个线程的终止。如果需要等待多个线程的终止,则需要分别调用 pthread_join() 函数。另外,如果不需要等待子线程的终止或获取其返回值,可以将 pthread_join() 函数的第二个参数设置为 NULL。
  6. pthread_join函数就会返回,并将该线程的返回值(如果有的话)存储在retval指向的内存中。同时,pthread_join函数还会回收被等待线程所占用的资源,以避免资源泄漏。
  7. 需要注意的是,一旦线程结束,它所占用的资源(包括线程栈、线程ID等)就会被系统回收,因此在调用pthread_join函数之前,我们需要确保被等待的线程已经结束,否则可能会导致pthread_join函数阻塞在那里,无法结束。

pthread_exit

pthread_exit函数是POSIX线程库中的一个函数,用于退出当前线程。它的原型如下:

void pthread_exit(void *retval);

参数解释:

  1. void *retval:线程的返回值。这是一个指向任意类型数据的指针,用于指定线程的返回值。如果线程没有返回值,则可以将该参数设置为NULL

返回值:pthread_exit函数不会返回任何值。

注意,使用pthread_exit函数退出线程时,必须小心线程的生命周期和资源管理。如果您没有正确地退出线程并释放其资源,可能会导致内存泄漏或其他问题。在退出线程之前,您需要确保释放线程分配的任何资源并释放任何锁或其他同步机制。

另外,pthread_exit函数只退出当前线程,并不影响其他线程的执行。如果您需要终止整个进程或应用程序,可以使用exit函数或类似的函数。在使用exit函数时,它将终止整个进程,并在退出前调用所有已注册的终止处理程序。

总的来说,pthread_exit函数是一个非常有用的函数,用于退出线程并返回线程的结果。它允许您在多线程应用程序中有效地管理线程的生命周期和资源。使用pthread_exit函数,可以避免线程泄漏和资源浪费,并确保线程在完成任务后正确退出。

需要注意的是,当一个线程调用pthread_exit函数退出时,它将释放它所占用的所有资源,并将返回值传递给调用它的线程。线程的返回值可以是任何类型的指针,包括结构体、数组、指针等等。在使用pthread_join函数等待线程完成并检索返回值时,您需要将返回值指针转换为适当的数据类型,并检查是否成功获取了返回值。

除了pthread_exit函数,POSIX线程库中还有许多其他函数,用于管理线程的生命周期和资源,包括:

  1. pthread_cancel:取消一个正在运行的线程。
  2. pthread_detach:将线程设置为分离状态,使其在退出时自动释放资源。
  3. pthread_attr_init:初始化线程属性结构。
  4. pthread_attr_destroy:销毁线程属性结构。
  5. pthread_attr_setstacksize:设置线程堆栈的大小。
  6. pthread_attr_setschedpolicy:设置线程的调度策略。
  7. pthread_attr_setschedparam:设置线程的调度参数(例如优先级)。
  8. pthread_mutex_init:初始化互斥锁。
  9. pthread_mutex_destroy:销毁互斥锁。
  10. pthread_mutex_lock:锁定互斥锁,使其他线程无法访问共享资源。
  11. pthread_mutex_unlock:解锁互斥锁,允许其他线程访问共享资源。
  12. pthread_cond_init:初始化条件变量。
  13. pthread_cond_destroy:销毁条件变量。
  14. pthread_cond_wait:等待条件变量,直到满足条件。
  15. pthread_cond_signal:唤醒等待条件变量的一个线程。
  16. pthread_cond_broadcast:唤醒等待条件变量的所有线程。

这些函数提供了丰富的线程管理和同步机制,可以帮助您构建高效、可靠的多线程应用程序

示例:

#include <stdio.h>
#include <pthread.h>
// int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *func1(void *arg)
{
	static int ret = 10;
	printf("t1:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t1:param is %d\n", *((int *)arg));
	pthread_exit((void *)&ret);
}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;
	int *pret = NULL;
	ret = pthread_create(&t1, NULL, func1, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t1 success\n");
	}
	printf("main:%ld\n", (unsigned long)pthread_self());
	pthread_join(t1, (void **)&pret);
	printf("main: t1 quit: %d\n", *pret);
	return 0;
}

image

#include <stdio.h>
#include <pthread.h>
void *func1(void *arg)
{
	static char *p = "t1 is run out";
	printf("t1:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t1:param is %d\n", *((int *)arg));
	pthread_exit((void *)p);
}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;
	char *pret = NULL;
	ret = pthread_create(&t1, NULL, func1, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t1 success\n");
	}
	printf("main:%ld\n", (unsigned long)pthread_self());
	pthread_join(t1, (void **)&pret);
	printf("main: t1 quit: %s\n", pret);
	return 0;
}

image

线程被取消

如果线程被取消,则可以使用 pthread_join() 函数来等待线程的取消。可以将 PTHREAD_CANCELED 常量作为线程的返回值,以检查线程是否已被取消:线程被取消时,其返回值将被设置为 PTHREAD_CANCELED,而不是线程函数中使用 pthread_exit() 函数设置的值。因此,在使用 pthread_join() 函数等待线程的取消时,应该检查其返回值是否为 PTHREAD_CANCELED。

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

void *thread_func(void *arg)
{
    int i;
    int *value = (int *)arg;
    printf("Child thread started, value = %d\n", *value);
    // 执行一些工作
    for (i = 0; i < 5; i++)
    {
        printf("Child thread working...\n");
        sleep(1);
    }
    // 线程被取消
    pthread_exit(PTHREAD_CANCELED);
}
int main()
{
    pthread_t thread;
    int value = 42;
    void *result;
    // 创建子线程
    if (pthread_create(&thread, NULL, thread_func, &value) != 0)
    {
        perror("pthread_create");
        exit(EXIT_FAILURE);
    }
    // 等待子线程的取消,并获取其返回值
    if (pthread_join(thread, &result) != 0)
    {
        perror("pthread_join");
        exit(EXIT_FAILURE);
    }
    if (result == PTHREAD_CANCELED)
    {
        printf("Child thread was canceled.\n");
    }
    else
    {
        printf("Child thread returned, value = %d\n", *(int *)result);
    }
    return 0;
}

image

多进程

#include <stdio.h>
#include <pthread.h>
int g_data = 0;
void *func1(void *arg)
{
	printf("t1:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t1:param is %d\n", *((int *)arg));
	while (1)
	{

		printf("t1: %d\n", g_data++);
		sleep(1);

		if (g_data == 3)
		{
			pthread_exit(NULL);
		}
	}
}
void *func2(void *arg)
{
	printf("t2:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t2:param is %d\n", *((int *)arg));
	while (1)
	{

		printf("t2: %d\n", g_data++);
		sleep(1);
	}
}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;
	pthread_t t2;
	ret = pthread_create(&t1, NULL, func1, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t1 success\n");
	}
	ret = pthread_create(&t2, NULL, func2, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t2 success\n");
	}
	printf("main:%ld\n", (unsigned long)pthread_self());
	while (1)
	{

		printf("main: %d\n", g_data++);
		sleep(1);
	}
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	return 0;
}

image

全局g_data的值再被一个线程调用时,其他线程就会阻塞,但线程执行的先后顺序并不能确定,这时就需要用到线程同步

线程同步是指在多线程编程中,为了保证多个线程协同工作时的正确性和可靠性,需要对它们的访问和操作进行协调和管理的机制。线程同步可以避免多个线程同时访问共享资源或变量导致的数据竞争和冲突,从而保证程序的正确性和稳定性。

常见的线程同步方式包括:

  1. 互斥锁:使用互斥锁可以确保同一时刻只有一个线程可以访问共享资源,其他线程需要等待锁被释放才能继续执行。
  2. 信号量:信号量是一个计数器,用于控制多个线程对共享资源的访问。当一个线程需要访问共享资源时,它需要获取信号量,如果信号量的值为0,则该线程需要等待其他线程释放信号量;如果信号量的值大于0,则该线程可以获取信号量并继续执行。当线程访问完共享资源后,需要释放信号量,以便其他线程可以获取信号量并继续执行。
  3. 事件:事件是一种同步机制,用于控制线程的执行顺序。当一个线程需要等待另一个线程执行完某个操作后才能继续执行时,它可以等待一个事件的触发。当另一个线程完成该操作后,它可以触发该事件,从而通知等待的线程可以继续执行。
  4. 读写锁:读写锁是一种特殊的互斥锁,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。当有线程需要写入共享资源时,其他线程需要等待写锁被释放才能继续执行。
  5. 条件变量:条件变量是一种同步机制,用于控制线程的等待和唤醒。当一个线程需要等待某个条件满足后才能继续执行时,它可以等待一个条件变量;当另一个线程满足该条件时,它可以通过唤醒条件变量的方式通知等待的线程可以继续执行。

线程同步的实现需要考虑多个方面,例如锁的粒度、死锁的预防、优先级反转等问题。如果线程同步实现不当,可能会导致程序出现各种问题,例如死锁、饥饿、优先级反转等。因此,在进行多线程编程时,需要仔细考虑线程同步的实现方式,并进行充分的测试和验证,以确保程序的正确性和稳定性。

此外,线程同步的实现也需要考虑性能问题。过多地使用锁、信号量等同步机制可能会导致线程间的竞争和阻塞,从而影响程序的性能。因此,在进行线程同步时,需要根据具体场景选择合适的同步机制,并进行合理的优化和调整,以达到最优的性能。

最后需要注意的是,线程同步实现需要根据具体的操作系统和编程语言进行不同的实现。不同的操作系统和编程语言可能提供不同的线程同步机制和API,因此需要根据具体的情况进行选择和实现。

互斥量

互斥锁(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。

互斥锁的使用步骤如下:

  1. 定义互斥锁变量:在使用互斥锁之前,需要先定义一个互斥锁变量,通常使用pthread_mutex_t类型的变量来表示互斥锁。
  2. 初始化互斥锁:在使用互斥锁之前,需要对互斥锁进行初始化,通常情况下,互斥锁都是动态分配的,使用 pthread_mutex_init() 函数进行初始化。
  3. 加锁:在访问共享资源之前,需要先加锁,以保证同一时刻只有一个线程可以访问共享资源。可以使用pthread_mutex_lock函数来加锁。
  4. 访问共享资源:在加锁成功后,可以对共享资源进行访问和修改。
  5. 解锁:在访问共享资源之后,需要解锁,以便其他线程可以访问共享资源。可以使用pthread_mutex_unlock函数来解锁。
  6. 销毁互斥锁:在不再需要使用互斥锁时,需要将其销毁,可以使用pthread_mutex_destroy函数来完成销毁操作。

需要注意的是,互斥锁只能保证同一时刻只有一个线程可以访问共享资源,但不能保证线程的执行顺序。如果多个线程同时请求锁,那么它们将会被阻塞,直到锁被释放为止。因此,在使用互斥锁时,需要注意避免死锁问题的发生。

下面是一个简单的互斥锁使用示例:

#include <stdio.h>
#include <pthread.h>
int g_data = 0;
pthread_mutex_t mutex;
void *func1(void *arg)
{
	int i;
	pthread_mutex_lock(&mutex);
	for (i = 0; i < 5; i++)
	{
		printf("t1:%ld thread is create\n", (unsigned long)pthread_self());
		printf("t1:param is %d\n", *((int *)arg));
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
	pthread_mutex_lock(&mutex);
	printf("t2:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t2:param is %d\n", *((int *)arg));
	pthread_mutex_unlock(&mutex);
}
void *func3(void *arg)
{
	pthread_mutex_lock(&mutex);
	printf("t3:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t3:param is %d\n", *((int *)arg));
	pthread_mutex_unlock(&mutex);
}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;
	pthread_t t2;
	pthread_t t3;
	pthread_mutex_init(&mutex, NULL);
	ret = pthread_create(&t1, NULL, func1, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t1 success\n");
	}
	ret = pthread_create(&t2, NULL, func2, (void *)&param);
	if (ret == 0)
	{
		printf("main:create t2 success\n");
	}
	ret = pthread_create(&t3, NULL, func3, (void *)&param);
	printf("main:%ld\n", (unsigned long)pthread_self());
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

image

在上述代码中,我们定义了一个pthread_mutex_t类型的互斥锁变量mutex,并使用pthread_mutex_init函数对其进行初始化。在print函数中,我们使用pthread_mutex_lock函数对互斥锁进行加锁操作,以便访问共享资源;访问完共享资源后,再使用pthread_mutex_unlock函数进行解锁操作。在main函数中,我们创建了两个线程t1和t2,并分别调用print函数,最后使用pthread_join函数等待线程执行结束,最后使用pthread_mutex_destroy函数销毁互斥锁。 需要注意的是,在使用互斥锁时,需要避免死锁的问题。死锁是指两个或多个线程互相等待对方释放锁的状态,导致程序无法继续执行的情况。为了避免死锁,需要注意以下几点:

  1. 避免嵌套锁:尽可能减少锁的嵌套,避免多个锁的交叉使用。
  2. 避免长时间持有锁:尽可能缩短锁的持有时间,避免一个线程持有锁的时间过长导致其他线程无法访问共享资源。
  3. 避免循环等待:尽可能避免出现循环等待的情况,即多个线程之间形成一个环,每个线程都在等待下一个线程释放锁的状态。
  4. 使用锁的顺序:对于多个锁的情况,需要按照同一顺序获取锁,避免不同线程获取锁的顺序不一致导致死锁。

除了上述注意事项之外,还需要注意互斥锁的性能问题。过多地使用锁可能会导致线程间的竞争和阻塞,从而影响程序的性能。因此,在使用互斥锁时,需要根据实际情况进行优化和调整,以达到最优的性能。

同时,为了避免互斥锁的使用带来的性能问题,还可以采用一些替代方案,例如读写锁、无锁编程等。

读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源,从而避免了锁的竞争和阻塞。使用读写锁可以提高程序的并发性能,特别是在读多写少的场景下。

无锁编程是指使用无锁算法或基于CAS操作的并发数据结构,避免了锁的使用,从而提高了程序的并发性能。无锁编程需要使用一些特殊的数据结构和算法,例如原子操作、无锁队列等。

总之,在进行多线程编程时,需要根据具体场景选择合适的同步机制,并注意同步机制的实现方式、性能问题以及死锁等问题。同时,需要注意线程安全和线程竞争的问题,避免出现数据竞争、死锁等问题,从而保证程序的正确性和稳定性。

一次性初始化:用宏是静态初始化

死锁

如果互斥锁的使用不当,就可能会出现死锁的情况。死锁是指两个或多个线程之间相互等待对方释放资源,导致程序无法继续执行的情况。

假设有两个进程 A 和 B,它们需要同时访问两个共享资源 X 和 Y,但是它们的访问顺序不同。如果 A 先访问了 X,然后请求 Y,而 B 先访问了 Y,然后请求 X,那么就有可能出现死锁。

#include <stdio.h>
#include <pthread.h>
int g_data = 0;
pthread_mutex_t mutex;
pthread_mutex_t mutex2;
void *func1(void *arg)
{
	int i;
	pthread_mutex_lock(&mutex);
	sleep(1);
	pthread_mutex_lock(&mutex2);
	for(i=0;i<5;i++){
		printf("t1:%ld thread is create\n",(unsigned long)pthread_self());
		printf("t1:param is %d\n",*((int *)arg));
		sleep(1);
	}
	pthread_mutex_unlock(&mutex);
}
void *func2(void *arg)
{
	pthread_mutex_lock(&mutex2);
	sleep(1);
	pthread_mutex_lock(&mutex);
	printf("t2:%ld thread is create\n",(unsigned long)pthread_self());
	printf("t2:param is %d\n",*((int *)arg));
	pthread_mutex_unlock(&mutex);

}
void *func3(void *arg)
{

	pthread_mutex_lock(&mutex);
	printf("t3:%ld thread is create\n",(unsigned long)pthread_self());
	printf("t3:param is %d\n",*((int *)arg));
	pthread_mutex_unlock(&mutex);

}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;
	pthread_t t2;
	pthread_t t3;
	pthread_mutex_init(&mutex, NULL);
	pthread_mutex_init(&mutex2, NULL);
	ret = pthread_create(&t1, NULL, func1,(void *)&param);
	if(ret == 0){
		printf("main:create t1 success\n");
	}
	ret = pthread_create(&t2, NULL, func2,(void *)&param);
	if(ret == 0){
		printf("main:create t2 success\n");
	}
	ret = pthread_create(&t3, NULL, func3,(void *)&param);
	printf("main:%ld\n",(unsigned long)pthread_self());
	pthread_join(t1,NULL);
	pthread_join(t2,NULL);
	pthread_mutex_destroy(&mutex);
	pthread_mutex_destroy(&mutex2);
	return 0;
}

如果出现死锁,可以尝试以下几种方法进行解决:

  1. 避免资源竞争:死锁通常是由于多个线程对共享资源的竞争导致的,因此可以尝试避免资源竞争,减少死锁的发生。

  2. 加锁顺序:死锁的发生通常与加锁顺序有关,因此可以尝试规定加锁的顺序,避免出现循环等待的情况。例如,可以规定所有线程按照相同的加锁顺序进行操作,避免出现竞争和死锁的情况。

  3. 超时机制:可以为互斥锁设置超时机制,当等待时间超过一定的阈值时,自动放弃获取锁,并进行其他处理。这样可以避免长时间等待导致程序无法继续执行的情况。

  4. 强制释放锁:如果出现死锁,可以尝试强制释放锁,让线程继续执行。不过,这种方法可能会导致数据异常和程序错误,因此需要谨慎使用,避免出现更严重的问题。

  5. 死锁检测:可以使用死锁检测工具,检测程序中是否存在死锁的情况,并自动解决死锁。例如,可以使用 Valgrind 工具进行死锁检测,及时发现和解决潜在的死锁问题。

    需要注意的是,在编写多线程程序时,要遵循正确的编程规范和约定,避免出现死锁、竞争等问题。同时,需要对程序进行充分测试和验证,确保程序的正确性和稳定性。

    总之,互斥锁是线程同步的重要机制,但如果使用不当,就可能会出现死锁的情况。如果出现死锁,可以尝试避免资源竞争,规定加锁顺序,设置超时机制,强制释放锁,或使用死锁检测工具等方法进行解决

条件变量

多线程条件变量通常用于实现线程之间的同步和互斥,使得线程能够在特定条件下等待或唤醒。在多线程编程中,条件变量经常与互斥锁一起使用,以实现对共享数据的并发访问的控制。

#include <stdio.h>
#include <pthread.h>
int g_data = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *func1(void *arg)
{
	printf("t1:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t1:param is %d\n", *((int *)arg));
	static int cnt = 0;
	while (1)
	{
		// 线程1等待条件发生再继续运行
		pthread_cond_wait(&cond, &mutex);
		printf("t1 run================================\n");
		printf("t1: %d\n", g_data);
		g_data = 0;
		sleep(1);
		if (cnt++ == 10)
		{
			exit(1);
		}
	}
}
void *func2(void *arg)
{
	printf("t2:%ld thread is create\n", (unsigned long)pthread_self());
	printf("t2:param is %d\n", *((int *)arg));
	while (1)
	{
		printf("t2: %d\n", g_data);
		pthread_mutex_lock(&mutex);
		g_data++;
		if (g_data == 3)
		{
			pthread_cond_signal(&cond);
		}
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}
int main()
{
	int ret;
	int param = 100;
	pthread_t t1;
	pthread_t t2;
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);
	ret = pthread_create(&t1, NULL, func1, (void *)&param);
	ret = pthread_create(&t2, NULL, func2, (void *)&param);
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	return 0;
}

image

读写锁

以下是一个使用C语言中的读写锁的示例代码,可以用于多个线程同时对共享资源进行读写操作:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_rwlock_t lock; // 读写锁变量
void *reader(void *arg)
{
    pthread_rwlock_rdlock(&lock); // 获取读锁
    printf("Reader %d is reading...\n", (int)arg);
    pthread_rwlock_unlock(&lock); // 释放读锁
    return NULL;
}
void *writer(void *arg)
{
    pthread_rwlock_wrlock(&lock); // 获取写锁
    printf("Writer %d is writing...\n", (int)arg);
    pthread_rwlock_unlock(&lock); // 释放写锁
    return NULL;
}
int main()
{
    int i;
    pthread_t threads[10];
    // 初始化读写锁变量
    pthread_rwlock_init(&lock, NULL);
    // 创建5个读者线程和5个写者线程
    for (i = 0; i < 5; i++)
    {
        pthread_create(&threads[i], NULL, reader, (void *)i);
        pthread_create(&threads[i + 5], NULL, writer, (void *)i);
    }
    // 等待所有线程执行完毕
    for (i = 0; i < 10; i++)
    {
        pthread_join(threads[i], NULL);
    }
    // 销毁读写锁变量
    pthread_rwlock_destroy(&lock);
    return 0;
}

image

在上面的代码中,我们首先定义了一个名为“lock”的全局读写锁变量。然后我们定义了两个函数——读者和写者。读者函数调用pthread_rwlock_rdlock函数获取读锁,打印一条消息表示正在读取,最后释放读锁。写者函数调用pthread_rwlock_wrlock函数获取写锁,打印一条消息表示正在写入,最后释放写锁。

在main函数中,我们首先初始化读写锁变量,然后创建5个读者线程和5个写者线程,每个线程都传递一个整数参数作为其ID。最后,我们等待所有线程完成,销毁读写锁变量,并返回0。

在实际使用中,读写锁可以帮助我们更好地处理共享资源的并发访问问题,提高程序的性能和稳定性。

读写锁的基本原理是:在读操作时允许多个线程同时访问共享资源,而在写操作时只允许单个线程访问共享资源。这样可以避免多个线程同时写入导致数据不一致的问题,提高程序的并发性和性能。

在使用读写锁时,我们需要注意以下几点:

  1. 在调用pthread_rwlock_rdlock函数获取读锁时,如果有其他线程正在持有写锁,则会阻塞等待;如果有其他线程正在持有读锁,则可以立即获取读锁。

  2. 在调用pthread_rwlock_wrlock函数获取写锁时,如果有其他线程正在持有任何类型的锁(读锁或写锁),则会阻塞等待,直到所有锁都被释放。

  3. 在调用pthread_rwlock_unlock函数释放锁时,需要确保当前线程之前已经成功获取了相应的锁,否则可能会导致程序出现未定义的行为。

  4. 在使用读写锁时,我们需要确保所有线程都正确地获取和释放了读写锁,否则可能导致程序出现死锁或其他严重问题。

    总之,读写锁是一种非常有用的并发控制机制,可以帮助我们更好地处理共享资源的并发访问问题。在使用读写锁时,我们需要仔细考虑各种情况,确保程序的正确性和稳定性。

注意事项

在 POSIX 线程中,互斥锁是用于控制线程之间访问共享资源的同步机制,而不是用于控制进程之间的同步。因此,互斥锁不能指定每个进程的运行优先级。

如果需要控制进程之间的运行顺序,可以使用进程间通信(IPC)机制,如信号量消息队列等。这些机制可以用于不同进程之间的同步和通信,从而实现控制进程之间的运行顺序。

需要注意的是,在使用 IPC 机制时,需要考虑进程间同步的问题,避免出现死锁等问题。通常需要合理设计同步机制,并遵循一定的编程规范和约定,以确保多个进程之间能够正确地进行同步和通信。

例如,使用信号量时,需要确保所有进程都遵循同样的信号量操作顺序,避免出现死锁等问题。此外,还需要考虑进程间通信的效率问题,避免出现过多的上下文切换和系统调用,影响进程的性能。

总之,互斥锁是线程同步的常用机制,而进程间通信机制则用于控制进程之间的同步和通信。在使用这些机制时,需要根据具体的应用场景和需求,选择合适的同步机制,并遵循相应的编程规范和约定,以确保程序的正确性和性能。同时,需要注意避免出现死锁、饥饿等问题,保证程序的稳定性和可靠性。

利用信号量控制线程的运行顺序

使用信号量可以实现对线程的同步和互斥控制,从而控制线程的运行顺序。下面是一个使用信号量控制线程运行顺序的示例代码:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem1, sem2;
void *thread1(void *arg)
{
    sem_wait(&sem1); // 等待信号量 sem1
    printf("Thread 1 is running.\n");
    sem_post(&sem2); // 发送信号量 sem2
    return NULL;
}

void *thread2(void *arg)
{
    sem_wait(&sem2); // 等待信号量 sem2
    printf("Thread 2 is running.\n");
    sem_post(&sem1); // 发送信号量sem1
    return NULL;
}
int main()
{
    pthread_t tid1, tid2;
    sem_init(&sem1, 0, 0);
    // 初始化信号量sem1
    sem_init(&sem2, 0, 0);
    // 初始化信号量sem2
    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);
    sem_post(&sem1); // 发送信号量 sem1
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    sem_destroy(&sem1); // 销毁信号量sem1
    sem_destroy(&sem2); // 销毁信号量sem2
    return 0;
}

在上面的代码中,我们创建了两个线程 thread1 和 thread2,它们都依赖信号量 sem1 和 sem2。在主线程中,我们首先初始化了信号量 sem1 和 sem2,然后创建了两个子线程。接着,我们发送了一个信号量 sem1,让 thread1 开始运行。在 thread1 中,它等待信号量 sem1,然后输出一条信息,并发送一个信号量 sem2。在 thread2 中,它等待信号量 sem2,然后输出一条信息,并发送一个信号量 sem1。最后,我们在主线程中等待两个子线程运行结束,并销毁了信号量 sem1 和 sem2。

可以看到,通过使用信号量,我们可以实现对线程的同步和互斥控制,从而控制线程的运行顺序。在上面的示例代码中,我们使用了两个信号量 sem1 和 sem2,通过发送和等待信号量的方式,实现了线程的同步和互斥控制。具体地,我们首先发送了一个信号量 sem1,让 thread1 开始运行,然后在 thread1 中等待信号量 sem1,输出一条信息后发送信号量 sem2。在 thread2 中,它等待信号量 sem2,输出一条信息后发送信号量 sem1。通过这种方式,我们实现了线程的交替运行,控制了线程的运行顺序。