Linux 多线程编程
韦东山的例程
所谓线程,就是操作系统所能调度的最小单位。普通的进程,只有一个线程在执行对应的逻辑。我们可以通过多线程编程,使一个进程可以去执行多个不同的任务。相比多进程编程而言,线程享有共享资源,即在进程中出现的全局变量,每个线程都可以去访问它,与进程共享内存空间,使得系统资源消耗减少。
创建线程,使用 pthread_create 函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
该函数第一个参数为 pthread_t 指针,用来保存新建线程的线程号;
- 第二个参数表示了线程的属性,一般传入 NULL 表示默认属性;
- 第三个参数是一个函数指针,就是线程执行的函数。这个函数返回值为 void*,形参为void*
- 第四个参数则表示为向线程处理函数传入的参数,若不传入,可用 NULL 填充
第一个例程:
创线程
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
static void *my_thread_func (void *data)
{
while (1)
{
sleep(1);
}
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
/* 1. 创建"接收线程" */
//my_thread_func将在线程创建时运行
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret)
{
printf("pthread_create err!\n");
return -1;
}
/* 2. 主线程读取标准输入, 发给"接收线程" */
while (1)
{
sleep(1);
}
return 0;
}
编译报错则 加 -lpthread 链接别的库
运行后查看进程 ps 查看线程 ps -T
第二个例程:
目的是创建一个接收线程,这个接收线程能接收主线程给的数据,主线程从标准输入得到数据
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
static char g_buf[1000];
//线程可以访问全局变量,加个标记位
static int g_hasData = 0;
static void *my_thread_func (void *data)
{
while (1)
{
//sleep(1);
/* 等待通知 */
while (g_hasData == 0);
/* 打印 */
printf("recv: %s\n", g_buf);
g_hasData = 0;
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
/* 1. 创建"接收线程" */
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret)
{
printf("pthread_create err!\n");
return -1;
}
/* 2. 主线程读取标准输入, 发给"接收线程" */
while (1)
{
fgets(g_buf, 1000, stdin);
/* 通知接收线程 */
g_hasData = 1;
}
return 0;
}
但是,这样的代码使得cpu资源消耗很大,fgets用于接收,未发送数据时,它就会处于休眠状态,然而 while(g_hasData == 0) 一直在运行,我们应该考虑让他修眠
///
第三个例程:
应该让主函数收到信号量再去唤醒接收线程
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
static char g_buf[1000];
static sem_t g_sem;
static void *my_thread_func (void *data)
{
while (1)
{
//sleep(1);
/* 等待通知 */
//while (g_hasData == 0);
sem_wait(&g_sem);
/* 打印 */
printf("recv: %s\n", g_buf);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
sem_init(&g_sem, 0, 0);
/* 1. 创建"接收线程" */
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret)
{
printf("pthread_create err!\n");
return -1;
}
/* 2. 主线程读取标准输入, 发给"接收线程" */
while (1)
{
fgets(g_buf, 1000, stdin);
/* 通知接收线程 */
sem_post(&g_sem);
}
return 0;
}
这样cpu占用高解决了
但是,接收线程在打印g_buf的时候,主线程也是在运行的,那么,接收线程在打印g_buf的时候如果输入了字符串,可能接收线程打印出来的可能半截老数据,半截新数据
/
第四个例程:
我们引入互斥量mutex
第一次代码,主线程接受没问题,子线程无法执行,无法打印
原因是主线程解锁后,可能立马又进入while,这个时候sem_post(&g_sem)却还没有到达子线程!
互斥量总是被主线程获得,子线程没那么快反应,无法获取互斥量
while (1)
{
pthread_mutex_lock(&g_tMutex);
fgets(buf, 1000, stdin);
pthread_mutex_unlock(&g_tMutex);
/* 通知接收线程 */
sem_post(&g_sem);
}
主线程获得互斥量时间太长,我们想办法让他短一点
读数据读临时buf,得到数据后拷入g_buf中去!
这样,主线程占用互斥锁的时间主要就是数据拷贝和设置g_hasData变量的时间,相比之前直接在获取互斥锁的情况下读取到g_buf,减少了在互斥锁保护下的fgets操作时间(fgets可能会因为等待用户输入而长时间占用互斥锁),所以先输完再说,加大子线程运行几率
while (1)
{
fgets(buf, 1000, stdin);
pthread_mutex_lock(&g_tMutex);
memcpy(g_buf, buf, 1000);
pthread_mutex_unlock(&g_tMutex);
/* 通知接收线程 */
sem_post(&g_sem);
}
这是完整代码
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
static char g_buf[1000];
static sem_t g_sem;
//定义互斥量
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static void *my_thread_func (void *data)
{
while (1)
{
//sleep(1);
/* 等待通知 */
//while (g_hasData == 0);
sem_wait(&g_sem);
/* 打印 */
pthread_mutex_lock(&g_tMutex);
printf("recv: %s\n", g_buf);
pthread_mutex_unlock(&g_tMutex);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
char buf[1000];
sem_init(&g_sem, 0, 0);
/* 1. 创建"接收线程" */
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret)
{
printf("pthread_create err!\n");
return -1;
}
/* 2. 主线程读取标准输入, 发给"接收线程" */
while (1)
{
fgets(buf, 1000, stdin);
pthread_mutex_lock(&g_tMutex);
memcpy(g_buf, buf, 1000);
pthread_mutex_unlock(&g_tMutex);
/* 通知接收线程 */
sem_post(&g_sem);
}
return 0;
}
第五个例程:
同步操作之条件变量
和互斥量同时使用
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
static char g_buf[1000];
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZER;
static void *my_thread_func (void *data)
{
while (1)
{
//sleep(1);
/* 等待通知 */
//while (g_hasData == 0);
pthread_mutex_lock(&g_tMutex);
pthread_cond_wait(&g_tConVar, &g_tMutex);
/* 打印 */
printf("recv: %s\n", g_buf);
pthread_mutex_unlock(&g_tMutex);
}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t tid;
int ret;
char buf[1000];
/* 1. 创建"接收线程" */
ret = pthread_create(&tid, NULL, my_thread_func, NULL);
if (ret)
{
printf("pthread_create err!\n");
return -1;
}
/* 2. 主线程读取标准输入, 发给"接收线程" */
while (1)
{
fgets(buf, 1000, stdin);
pthread_mutex_lock(&g_tMutex);
memcpy(g_buf, buf, 1000);
pthread_cond_signal(&g_tConVar); /* 通知接收线程 */
pthread_mutex_unlock(&g_tMutex);
}
return 0;
}