学习Linux线程:通过实例掌握基本知识 (linux thread 例子)
Linux是目前最为流行的操作系统之一,它的强大之处在于其开放性和灵活性。作为一名Linux开发者或者系统管理员,深入了解Linux线程是非常重要的一步。
本文将通过实例来帮助读者掌握Linux线程的基本知识。我们将使用C语言和Linux的pthread库来创建、管理和控制线程,并介绍一些基本的线程概念和技术。
为了更好地理解本文中的示例代码,我们假设读者已经了解C语言基础和Linux基础知识,并熟悉使用Linux的命令行界面。
一、线程基础概念
在开始讲解Linux线程之前,我们先了解一些基本概念。
线程是计算机操作系统中的一个执行单位,它是作系统独立调度和管理的最小运行单位。一个进程可以包含多个线程,多个线程可以同时执行,并共享进程的资源。
与进程不同,线程之间共享了进程的资源(堆、全局变量、静态变量等),因此线程间的通信也更加方便。此外,线程的创建和销毁比进程更加轻量级和快速。因此,线程在多任务操作中有着重要的应用。
二、线程的创建和销毁
接下来我们通过一个简单的例子来创建和销毁线程。
#include
#include
#include
void *hello_thread(void *arg)
{
int i;
for (i = 0; i
{
printf(“hello thread!\n”);
sleep(1);
}
return NULL;
}
int mn(int argc, char *argv[])
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, hello_thread, NULL);
if (ret != 0)
{
printf(“pthread_create error:ret=%d\n”, ret);
return -1;
}
printf(“create thread %lu\n”, tid);
pthread_join(tid, NULL);
return 0;
}
上述代码中,我们首先定义了一个hello_thread函数,用于线程执行的任务。接着,我们在mn函数中使用pthread_create函数创建了一个新的线程,传入线程ID、线程属性、线程函数以及传递给线程函数的参数。线程创建成功后,主线程会打印出创建的线程ID。我们使用pthread_join函数等待线程的结束。
你可以使用gcc编译该代码,如下所示:
$ gcc -o thread thread.c -lpthread
执行编译后的可执行文件,你将看到如下输出:
create thread 3079033696
hello thread!
hello thread!
hello thread!
上述示例中,我们调用了pthread_create函数来创建一个线程。pthread_create函数的之一个参数传入要创建的线程ID,第二个参数指定线程的属性(通常为NULL),第三个参数是线程函数的指针(函数名不需加括号),最后一个参数是传递给线程函数的参数(通常为NULL)。
线程创建成功后,主线程继续执行,而新线程开始执行其指定的函数。在我们的示例中,新线程是输出”hello thread!” 三次,每次隔1秒钟,然后退出。当线程函数返回NULL时,表示线程任务结束。主线程调用pthread_join等待子线程完成。
三、线程同步
在多线程编程中,线程同步是非常重要的概念。特别是在共享资源的情况下,要确保线程的正确性和一致性。
我们以生产者消费者为例,讲述如何使用线程同步来确保数据的正确性。这里我们假设有一个环形缓冲区,它被一个单独的线程用于生产数据,一个线程用于消费数据。生产者在存储数据时会检查环形缓冲区是否已满,消费者在取出数据时会检查缓冲区是否为空。
我们使用两个互斥锁(pthread_mutex_t)和两个条件变量(pthread_cond_t)来实现上述场景。
#include
#include
#include
#define BUFFER_SIZE 4
int buffer[BUFFER_SIZE];
int g_write_idx = 0;
int g_read_idx = 0;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t g_notempty_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t g_notfull_cond = PTHREAD_COND_INITIALIZER;
void *producer_thread(void *arg)
{
int i;
for (i = 0; i
{
pthread_mutex_lock(&g_mutex);
while (g_write_idx == BUFFER_SIZE)
{
pthread_cond_wt(&g_notfull_cond, &g_mutex);
}
buffer[g_write_idx++] = i;
printf(“produce: %d\n”, i);
pthread_cond_signal(&g_notempty_cond);
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
void *consumer_thread(void *arg)
{
int i, data;
for (i = 0; i
{
pthread_mutex_lock(&g_mutex);
while (g_write_idx == 0)
{
pthread_cond_wt(&g_notempty_cond, &g_mutex);
}
data = buffer[g_read_idx++];
printf(“consume: %d\n”, data);
pthread_cond_signal(&g_notfull_cond);
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
int mn(int argc, char *argv[])
{
pthread_t producer_tid, consumer_tid;
pthread_create(&producer_tid, NULL, producer_thread, NULL);
pthread_create(&consumer_tid, NULL, consumer_thread, NULL);
pthread_join(producer_tid, NULL);
pthread_join(consumer_tid, NULL);
return 0;
}
上述代码中,我们首先定义BUFFER_SIZE表示缓冲区的大小,然后定义g_write_idx和g_read_idx分别表示写下标和读下标。接着我们定义了两个互斥锁g_mutex和两个条件变量g_notempty_cond和g_notfull_cond。
在生产者线程中,我们使用pthread_mutex_lock函数来访问共享资源buffer和g_write_idx,如果发现缓冲区已满,线程将使用pthread_cond_wt函数等待g_notfull_cond条件变量。当消费者取出数据后,生产者线程将使用pthread_cond_signal函数通知消费者线程缓冲区中已经有可读数据,然后释放互斥锁。
在消费者线程中,我们也使用pthread_mutex_lock函数来访问共享资源buffer和g_read_idx,如果发现缓冲区为空,线程将使用pthread_cond_wt函数等待g_notempty_cond条件变量。当生产者存储了新数据后,消费者线程将使用pthread_cond_signal函数通知生产者线程缓冲区中已经有空闲空间,然后释放互斥锁。
在主函数中,我们调用pthread_create函数来创建生产者线程和消费者线程,并使用pthread_join等待两个线程结束。
四、线程池
线程池是一种常见的多线程技术,它可以在应用程序中管理和调度大量线程。线程池中的线程通常是预先创建好的,它们将等待任务的到来并处理任务。使用线程池可以避免线程频繁创建和销毁的开销,提高多线程的效率。
下面我们介绍如何使用线程池来执行一个简单的任务。
#include
#include
#include
#include
#include
#define DEFAULT_THREAD_NUM 3
typedef struct task_s
{
void *(*task_func)(void *);
void *arg;
struct task_s *next;
} task_t;
typedef struct threadpool_s
{
int thread_num;
pthread_t *threads;
pthread_mutex_t lock;
pthread_cond_t cond;
task_t *head;
task_t *tl;
int shutdown;
} threadpool_t;
void *threadpool_worker(void *arg)
{
threadpool_t *pool = (threadpool_t *)arg;
task_t *task = NULL;
while (1)
{
pthread_mutex_lock(&pool->lock);
while (!pool->shutdown && !pool->head)
{
pthread_cond_wt(&pool->cond, &pool->lock);
}
if (pool->shutdown)
{
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
}
task = pool->head;
pool->head = task->next;
if (pool->head == NULL)
{
pool->tl = NULL;
}
pthread_mutex_unlock(&pool->lock);
printf(“Do something…\n”);
task->task_func(task->arg);
free(task);
}
return NULL;
}
int threadpool_create(threadpool_t *pool, int thread_num)
{
int i;
pool->thread_num = thread_num;
pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_num);
if (pool->threads == NULL)
{
perror(“malloc”);
return -1;
}
pool->head = NULL;
pool->tl = NULL;
if (pthread_mutex_init(&pool->lock, NULL) != 0)
{
perror(“pthread_mutex_init”);
return -1;
}
if (pthread_cond_init(&pool->cond, NULL) != 0)
{
perror(“pthread_cond_init”);
return -1;
}
pool->shutdown = 0;
for (i = 0; i
{
if (pthread_create(&pool->threads[i], NULL, threadpool_worker, (void *)pool) != 0)
{
perror(“pthread_create”);
return -1;
}
}
return 0;
}
void threadpool_destroy(threadpool_t *pool)
{
int i;
task_t *task;
if (pool->shutdown)
{
return;
}
pool->shutdown = 1;
pthread_cond_broadcast(&pool->cond);
for (i = 0; i thread_num; i++)
{
pthread_join(pool->threads[i], NULL);
}
free(pool->threads);
while (pool->head)
{
task = pool->head;
pool->head = task->next;
free(task);
}
pool->head = NULL;
pool->tl = NULL;
pthread_mutex_destroy(&pool->lock);
pthread_cond_destroy(&pool->cond);
}
int threadpool_add_task(threadpool_t *pool, void *(*task_func)(void *), void *arg)
{
task_t *task = (task_t *)malloc(sizeof(task_t));
if (task == NULL)
{
return -1;
}
task->next = NULL;
task->task_func = task_func;
task->arg = arg;
pthread_mutex_lock(&pool->lock);
if (pool->tl == NULL)
{
pool->head = task;
pool->tl = task;
}
else
{
pool->tl->next = task;
pool->tl = task;
}
pthread_cond_signal(&pool->cond);
pthread_mutex_unlock(&pool->lock);
return 0;
}
void *task_func(void *arg)
{
char *str = (char *)arg;
sleep(1);
printf(“Task is doing (%s)…\n”, str);
return NULL;
}
int mn(int argc, const char *argv[])
{
int i;
threadpool_t pool;
if (threadpool_create(&pool, DEFAULT_THREAD_NUM) != 0)
{
perror(“threadpool_create”);
return -1;
}
for (i = 0; i
{
char str[128] = {0};
sprintf(str, “task_%d”, i);
threadpool_add_task(&pool, task_func, strdup(str));
}
sleep(5);
threadpool_destroy(&pool);
return 0;
}
上述代码中,我们首先定义了一个task_t结构体,表示任务的数据结构。然后定义了一个threadpool_t结构体,表示线程池的数据结构。
在线程池的创建函数threadpool_create中,我们首先动态分配了线程数组,然后初始化了线程池的锁和条件变量。接着,我们使用pthread_create函数创建了指定数量的工作线程,并在这些线程中不断从线程池中取出任务进行处理。
在线程池的销毁函数threadpool_destroy中,我们首先通知所有工作线程退出,然后等待所有线程并成功退出。我们释放线程数组和任务队列中的所有任务,销毁线程池的锁和条件变量。
在向线程池添加任务时,我们创建一个新的任务并加入到线程池的任务队列中。这里我们使用了pthread_mutex_lock和pthread_cond_signal函数来保证多个线程都正确地访问任务队列。
在任务的执行函数task_func中,我们仅仅是简单地休眠了一秒钟,然后输出一个字符串。
在主函数中,我们使用threadpool_create函数来创建线程池并向线程池中加入了10个需要执行的任务,然后等待5秒钟后,使用threadpool_destroy函数销毁线程池。