c 线程(平行世界)

2022-12-02,,

我们已经知道如何使用进程来做一些事情了,然而 它并不是在什么地方都是最适合的。

我们看看进程的缺点是什么:

线程隆重登场

1. 如何创建线程

创建线程可以使用多种线程库,在此我们使用最流行的一种:POSIX线程库,也叫pthread。

假设有两个函数

 void * dose_do(void * a) {

     for (int i = ; i < ; i++) {
sleep();
puts("does_do");
} return NULL;
}
 void * dose_not(void * a) {

     for (int i = ; i < ; i++) {
sleep();
puts("does_not");
} return NULL;
}

这两个函数都返回了void指针,因为void指针可以指向存储器中任何数据类型的数据,线程函数的返回类必须是void *。

必须包含#include <pthread.h>头文件

我们使用pthread_create() 函数创建并运行一个线程,而且每个线程都需要把线程信息保存在一个pthread_t类型的数据中。

 // new pthread
pthread_t t0;
pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -) {
error("无法创建线程t0");
}
if (pthread_create(&t1, NULL, dose_do, NULL) == -) {
error("无法创建线程t1");
}

上边的两个函数将会独立的在线程中运行,知道结束,但是我们需要知道这两个函数什么时候结束。

我们使用pthread_join()函数等待函数结束,他会接受线程函数的返回值,并保存在一个void *类型的数据中。

那么这个函数是如何得知线程结束的呢?当得到线程函数的返回值的时候,就表明线程函数结束了。这也是为什么线程函数必须要有返回值的原因。

  void *result;
if (pthread_join(t0, &result) == -) {
error("无法回收线程t0");
}
if (pthread_join(t1, &result) == -) {
error("无法回收线程t1");
}

我们来看 全部代码

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // 错误处理函数
void error(char *msg) {
fprintf(stderr, "Error: %s %s", msg, strerror(errno));
exit();
} void * dose_not(void * a) { for (int i = ; i < ; i++) {
sleep();
puts("does_not");
} return NULL;
} void * dose_do(void * a) { for (int i = ; i < ; i++) {
sleep();
puts("does_do");
} return NULL;
} int main(int argc, const char * argv[]) { // new pthread
pthread_t t0;
pthread_t t1; if (pthread_create(&t0, NULL, dose_not, NULL) == -) {
error("无法创建线程t0");
}
if (pthread_create(&t1, NULL, dose_do, NULL) == -) {
error("无法创建线程t1");
} void *result;
if (pthread_join(t0, &result) == -) {
error("无法回收线程t0");
}
if (pthread_join(t1, &result) == -) {
error("无法回收线程t1");
} return ;
}

结果如下

再来看下边这段代码,我们有2000000瓶啤酒,开启20条线程,看最后剩余多少瓶?

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // 错误处理函数
void error(char *msg) {
fprintf(stderr, "Error: %s %s", msg, strerror(errno));
exit();
} int beers = ; void * drink_lots(void * a) { for (int i = ; i < ; i++) { beers = beers - ; } printf("剩余 %i 瓶啤酒 \n",beers); return NULL;
} int main(int argc, const char * argv[]) { // new pthread
pthread_t pthreads[]; printf("%i 瓶啤酒 \n",beers); for (int i = ; i < ; i++) { if (pthread_create(&pthreads[i], NULL, drink_lots, NULL) == -) {
error("无法创建线程");
}
} void *result; for (int i = ; i < ; i++) { if (pthread_join(pthreads[i], &result) == -) {
error("无法回收线程");
}
} return ;
}

运行结果

那么问题来了,为什么跟我们想要的结果不一样呢? 其实都点编程经验的人都知道,线程是不安全的。

要想解决这样的问题就要使用互斥锁

2. 用互斥锁保护线程

互斥锁必须对所有可能发生冲突的线程可见,也就是说它是一个全局变量。

创建:

pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER;

加锁

pthread_mutex_lock(&beers_lock);

解锁

pthread_mutex_unlock(&beers_lock);

我们修改上边的关于啤酒的函数为

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h> // 错误处理函数
void error(char *msg) {
fprintf(stderr, "Error: %s %s", msg, strerror(errno));
exit();
} pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER; int beers = ; void * drink_lots(void * a) { for (int i = ; i < ; i++) { pthread_mutex_lock(&beers_lock);
beers = beers - ;
pthread_mutex_unlock(&beers_lock);
} printf("剩余 %i 瓶啤酒 \n",beers); return NULL;
} int main(int argc, const char * argv[]) { // new pthread
pthread_t pthreads[]; printf("%i 瓶啤酒 \n",beers); for (int i = ; i < ; i++) { if (pthread_create(&pthreads[i], NULL, drink_lots, NULL) == -) {
error("无法创建线程");
}
} void *result; for (int i = ; i < ; i++) { if (pthread_join(pthreads[i], &result) == -) {
error("无法回收线程");
}
} return ;
}

运行结果如下

每个线程中循环结束后才会打印结果,也就是说当循环完之后打印的结果就是那个时间点还剩多少瓶啤酒。

线程的知识和运用先简单介绍到这,后续会增加实战的内容。

c 线程(平行世界)的相关教程结束。

《c 线程(平行世界).doc》

下载本文的Word格式文档,以方便收藏与打印。