学习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函数销毁线程池。


数据运维技术 » 学习Linux线程:通过实例掌握基本知识 (linux thread 例子)