作为一名软件开发者,我们一直在寻找提高程序效率的方法。在这个需要大量数据处理、高效运算的时代,多线程编程技术成为了越来越重要的一部分。Linux系统作为开放源代码的操作系统,具有优秀的多线程编程特性,能够帮助我们更轻松地优化程序性能。本文将围绕着“”这一主题,详细介绍Linux多线程编程的知识点和常见应用场景,并且提供一些实用技巧,希望能够帮助大家更好的学习和运用Linux多线程编程技术。
一、多线程编程的基础知识
1. 线程和进程的区别
在进行Linux多线程编程之前,我们需要先了解线程和进程的区别。在操作系统中,进程是资源分配的最小单位,而线程是程序执行的最小单位。一个进程可以包含多个线程,在同一个进程中的多个线程可以共享同一块内存空间,通信很容易。因为线程比进程轻量级,创建和销毁的开销非常小,所以在实现并行计算和任务处理时十分简便。
2. 创建线程的方式
在Linux下,创建线程通常采用pthread库。pthread 库是 Linux 上实现线程的一个标准库。常用的创建线程的函数有两个:pthread_create() 和 pthread_join() 函数。pthread_create() 函数用来创建线程,而 pthread_join() 函数则用来等待线程退出并释放资源。下面是一个最基本的线程创建例子:
```
#include
#include
#include
void *thread_func(void *arg)
{
printf("Hello, world!\n");
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_func, NULL) != 0)
{
printf("Failed to create thread\n");
return 1;
}
if (pthread_join(tid, NULL) != 0)
{
printf("Failed to join thread\n");
return 1;
}
return 0;
}
```
这个例子中调用了pthread_create() 函数来创建一个线程,传递参数:线程ID,线程属性,线程函数及参数。最后使用pthread_join() 函数等待该线程结束。
3. 线程同步和互斥
在线程编程中,线程同步和互斥也是比较常见的问题。线程同步的含义是确保两个或多个线程在同一时间共享资源。互斥是线程之间互相排斥,当一个线程使用一个共享资源时,其他线程必须等待,直到该线程完成对资源的使用。下面是一个互斥锁的例子:
```
#include
#include
#include
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int g_count = 0;
void *thread_func(void *arg)
{
int i;
int id = *(int *)arg;
for (i = 0; i < 100; ++i)
{
pthread_mutex_lock(&mutex);
++g_count;
printf("Thread #%d: g_count = %d\n", id, g_count);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t tids[2];
int ids[2] = {1, 2};
int i;
for (i = 0; i < 2; ++i)
{
if (pthread_create(&tids[i], NULL, thread_func, &ids[i]) != 0)
{
printf("Failed to create thread #%d\n", i+1);
return 1;
}
}
for (i = 0; i < 2; ++i)
{
if (pthread_join(tids[i], NULL) != 0)
{
printf("Failed to join thread #%d\n", i+1);
return 1;
}
}
return 0;
}
```
这个例子中使用了一个静态的互斥锁mutex,使用pthread_mutex_lock()函数加锁,pthread_mutex_unlock() 函数解锁。当一个线程进入printf 代码段时,使用pthread_mutex_lock() 函数将互斥锁锁住,保证另一个线程无法进入,从而保证每个线程更改g_count时,都是在一个线程执行完了之后才执行下一个线程。使用互斥锁可以保证多个线程之间的共享变量不会发生冲突,保证程序的安全性。
4. 线程池
线程池是一种这样的技术,即在程序启动时创建一些线程,然后将一些工作分配给它们,无需每次新建线程。使用线程池技术可以提高程序效率,减少系统开销。有两种类型的线程池:固定线程池和动态线程池。固定线程池创建一定数量的线程,并且在线程池中分配固定个数的任务队列。在任务队列满时新的任务会阻塞等待。而动态线程池则根据任务量的大小自动调节线程数量。下面是一个线程池的例子:
```
#include
#include
#include
typedef struct {
int busy; // 是否正在工作
pthread_t thread; // 工作的线程ID
int sockfd; // 传递给线程的socket
} thread_worker;
static const int kMaxWorkerNum = 8;
static thread_worker *g_thread_workers = NULL;
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
static int g_timeout = 30; // 空闲线程超时时间
void *thread_func(void *arg)
{
int sockfd;
pthread_t tid = pthread_self();
int i;
while (1)
{
pthread_mutex_lock(&g_mutex);
for (i = 0; i < kMaxWorkerNum; ++i)
if (g_thread_workers[i].thread == tid)
break;
if (i < kMaxWorkerNum)
{
if (g_thread_workers[i].busy == 0) // 线程空闲
{
if (pthread_cond_wait(&g_cond, &g_mutex) != 0) // 等待唤醒
{
perror("Failed to wait on condition");
}
continue;
}
sockfd = g_thread_workers[i].sockfd;
g_thread_workers[i].busy = 0;
pthread_mutex_unlock(&g_mutex);
// 处理任务
// ...
close(sockfd);
}
else
{
pthread_mutex_unlock(&g_mutex);
}
}
return NULL;
}
int add_worker(int sockfd)
{
int i;
for (i = 0; i < kMaxWorkerNum; ++i)
{
if (g_thread_workers[i].busy == 0)
{
g_thread_workers[i].busy = 1;
g_thread_workers[i].sockfd = sockfd;
if (pthread_cond_signal(&g_cond) != 0) // 唤醒一个线程
{
perror("Failed to signal condition");
}
return 0;
}
}
// all threads are busy
return -1;
}
int remove_worker(pthread_t tid)
{
int i;
for (i = 0; i < kMaxWorkerNum; ++i)
{
if (g_thread_workers[i].thread == tid)
{
g_thread_workers[i].thread = 0;
g_thread_workers[i].busy = 0;
g_thread_workers[i].sockfd = -1;
return 0;
}
}
// no such thread
return -1;
}
int start_worker_threads(void)
{
int i;
g_thread_workers = (thread_worker *)calloc(kMaxWorkerNum, sizeof(thread_worker));
if (g_thread_workers == NULL)
{
perror("Failed to allocate memory");
return -1;
}
for (i = 0; i < kMaxWorkerNum; ++i)
{
if (pthread_create(&g_thread_workers[i].thread, NULL, thread_func, NULL) != 0)
{
perror("Failed to create thread");
return -1;
}
}
return 0;
}
void stop_worker_threads(void)
{
int i;
for (i = 0; i < kMaxWorkerNum; ++i)
{
pthread_cancel(g_thread_workers[i].thread);
pthread_join(g_thread_workers[i].thread, NULL);
}
free(g_thread_workers);
}
int main(int argc, char *argv[])
{
int sockfd;
if (start_worker_threads() != 0)
{
printf("Failed to start worker threads\n");
return 1;
}
while (1)
{
sockfd = accept(...); // 接收连接
if (sockfd != -1)
{
add_worker(sockfd);
}
}
stop_worker_threads();
return 0;
}
```
这个例子中创建了一个最大容纳8个线程的线程池,线程会执行thread_func() 函数。在主线程中,使用accept()函数等待客户端连接,并将连接的socket传递给空闲线程池处理,即调用add_worker() 函数。线程在处理完任务后,通知主线程已经处理完成,并进入缓冲队列等待下一个任务,即调用pthread_cond_wait() 函数,并把is_busy设置为0。
在主线程中,add_worker() 函数在找到空闲线程后,会将socket存储到线程的is_busy和sockfd变量中,并唤醒其中一个线程池线程去处理。remove_worker() 函数和start_worker_threads() 函数的实现是删除和创建线程的逆操作。当所有线程处理完已有任务后,主线程执行stop_worker_threads() 函数停止线程池中的所有线程,关闭socket并释放资源。
二、Linux多线程编程的应用
除了上述介绍的线程池,Linux下的多线程编程也有许多应用场景。下面是一些常见的应用场景以及使用多线程编程的例子。
1. 多线程服务器
多线程服务器指的是在一个服务器中处理多个请求。传统的多进程服务器在每个连接中都创建了进程来处理,如果同时连接的客户端数量很多,需要创建大量进程,极容易导致系统负担过重,从而导致服务器宕机。
多线程服务器对我们很有帮助,它可以优化服务器并发度问题,提高处理请求效率。在多线程服务器中,我们可以使用线程池技术和多线程技术。多线程服务器在创建一个服务器线程、监听并接收用户请求后,将请求任务封装成一个任务队列,由线程池中的线程依次处理,使得单个线程可以同时处理多个请求,这样大大提高了服务器的并发度。下面是一个基本的多线程服务器实现:
```
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 8080
#define BACKLOG 32
#define MAX_CLIENTS 64
typedef struct {
pthread_t thread; // 线程ID
int sockfd; // socket文件描述符
} client_info;
static int g_listenfd; // 监听socket文件描述符
static int g_stop = 0; // 是否停止服务器
static int g_num_clients = 0; // 当前连接数量
static client_info g_clients[MAX_CLIENTS]; // 客户端信息结构体数组
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
void *handle_client(void *arg)
{
char buf[512];
int clientfd = *(int *)arg;
int nread;
while ((nread = read(clientfd, buf, 512)) > 0)
{
if (write(clientfd, buf, nread) == -1)
{
perror("Failed to write to socket");
break;
}
}
close(clientfd);
pthread_mutex_lock(&g_mutex);
int i;
for (i = 0; i < g_num_clients; ++i)
{
if (clientfd == g_clients[i].sockfd)
{
printf("Client %d disconnected\n", clientfd);
g_clients[i].sockfd = -1;
g_clients[i].thread = 0;
--g_num_clients;
break;
}
}
pthread_mutex_unlock(&g_mutex);
return NULL;
}
void *accept_clients(void *arg)
{
int clientfd;
int i;
while (!g_stop)
{
clientfd = accept(g_listenfd, NULL, NULL);
if (clientfd == -1)
{
perror("Failed to accept connection");
continue;
}
pthread_mutex_lock(&g_mutex);
if (g_num_clients < MAX_CLIENTS)
{
for (i = 0; i < MAX_CLIENTS; ++i)
{
if (g_clients[i].sockfd == -1)
{
g_clients[i].sockfd = clientfd;
if (pthread_create(&g_clients[i].thread, NULL,
handle_client, &g_clients[i].sockfd) != 0)
{
perror("Failed to create thread");
g_clients[i].sockfd = -1;
}
else
{
++g_num_clients;
printf("Client %d connected\n", clientfd);
}
break;
}
}
}
if (i == MAX_CLIENTS)
{
printf("Too many clients, closing the connection\n");
close(clientfd);
}
pthread_mutex_unlock(&g_mutex);
}
return NULL;
}
int start_server(void)
{
int ret;
struct sockaddr_in server_addr;
g_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (g_listenfd == -1)
{
perror("Failed to create socket");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
ret = bind(g_listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret == -1)
{
perror("Failed to bind address");
return -1;
}
ret = listen(g_listenfd, BACKLOG);
if (ret == -1)
{
perror("Failed to listen on socket");
return -1;
}
return 0;
}
void stop_server(void)
{
close(g_listenfd);
g_stop = 1;
}
int main(int argc, char *argv[])
{
if (start_server() != 0)
{
printf("Failed to start server\n");
return 1;
}
pthread_t accept_tid;
pthread_create(&accept_tid, NULL, accept_clients, NULL);
getchar();
stop_server();
pthread_join(accept_tid, NULL);
pthread_mutex_lock(&g_mutex);
int i;
for (i = 0; i < MAX_CLIENTS; ++i)
{
if (g_clients[i].sockfd != -1)
{
close(g_clients[i].sockfd);
pthread_join(g_clients[i].thread, NULL);
}
}
pthread_mutex_unlock(&g_mutex);
return 0;
}
```
这个例子中创建了一个多线程服务器,服务器启动时会创建一个监听socket,并接收客户端连接,将客户端socket文件描述符存储到客户端信息结构体数组g_clients中。在调用accept_clients()函数时,会创建一个线程来监听客户端连接,等待客户端请求。如果有客户端连接,将客户端socket文件描述符传递到空闲线程中处理,并将该socket存储到客户端信息结构体数组中。当客户端关闭连接后,将客户端信息结构体数组中的对应文件描述符设置为-1,并在客户端信息结构体数组中删除该元素。
2. 多线程排序
多线程排序在大数据排列、并行计算等方面