HeadFirstC笔记_12 线程:平行世界
来源:互联网 发布:免费网络视频会议系统 编辑:程序博客网 时间:2024/04/29 04:30
难道每当想要同时做几件事时都得创建进程吗?
不见得,有以下几个原因:
1.创建进程要花时间
有的机器新建进程只要花一丁点时间。虽然时间很短,但还是需要时间。如果你想要执行的任务才用几十毫秒,每次都创建进程就很低效。
2.共享数据不方便
当创建子进程时,子进程会自动包含父进程所有数据的副本。但这些只是副本,如果子进程想把数据发回父进程,就需要借助管道之类的东西。
3.进程真的很难
创建进程需要写很多代码,这会让代码又乱又长。
你需要线程
普通进程一次只做一件事
到目前为止,你写过的所有程序都是单线程,这就好比进程中只有一个人在干活。
多雇几名员工:使用线程
你可以雇多名员工在店里干活,同样,也可以在一个进程
中使用多个线程。所有线程能访问同一段堆存储器,读
写同一个文件,使用同一个网络套接字进行通信。当一个
线程修改了某个全局变量,其他线程马上就能看到。
也就是说,可以为每个线程都分配一个独立的任务,让这
些线程同时执行。
如何创建线程
你可以使用很多线程库,这里我们将使用最流行的一
种:POSIX线程库,也叫 pthread 。可以在Cygwin、Linux
和Mac上使用 pthread 。
假设你想在独立的线程中运行这个函数:
void* does_not(void *a)
{
int i = 0;
for (i = 0; i < 5; i++) {
sleep(1);
puts("Does not!");
}
return NULL;
}
注意:void指针可以指向存储器中任何类型的数据,线程函数的返回类型必须是 void* !
用pthread_create创建线程
每个线程都需
要把信息保存在一个叫 pthread_t 的数据结构中,然后就可以用
pthread_create() 创建并运行线程。
#include <pthread.h>
- ...
pthread_t t0; //它保存了线程的所有信息
pthread_create(&t0, NULL, does_not, NULL);
代码将以独立线程运行这两个函数。还没完,如果程序运行完这段代
码就结束了,线程也会随之灭亡,因此必须等待线程结束:
// 函数返回的void指针会保存在这里
void* result;
//pthread_join()函数会等待线程结束。
pthread_join(t0,&result);
pthread_join() 会接收线程函数的返回值,并把它保存在一个void 指针变量中。一旦两个线程都结束了,程序就可以顺利退出了。
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
// 打印错误的函数
void error(char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}
void* does_not(void *a) {
int i = 0;
for (i = 0; i < 5; i++) {
sleep(1);
puts("Does not!");
}
return NULL;
}
void* does_too(void *a) {
int i = 0;
for (i = 0; i < 5; i++) {
sleep(1);
puts("Does too!");
}
return NULL;
}
int main() {
pthread_t t0; //它保存了线程的所有信息
pthread_t t1;
// 创建线程
if (pthread_create(&t0, NULL, does_not, NULL) == -1) //does_not是线程将运行的函数名
error("无法创建线程t0"); //每次都应该检查错误。
if (pthread_create(&t1, NULL, does_too, NULL) == -1) //does_not是线程将运行的函数名
error("无法创建线程t1"); //每次都应该检查错误。
// 函数返回的void指针会保存在这里
void* result;
//pthread_join()函数会等待线程结束。
if(pthread_join(t0,&result)==-1)
error("无法回收线程t0");
if(pthread_join(t1,&result)==-1)
error("无法回收线程t1");
return 0;
}
运行结果:
如果不能编译,可以在编译时使用 pthread 库:
命令:gcc dchapter12.c -lpthread -o dchapter12
练习:
派对开始了,倒计数啤酒瓶数。下面这段代码运行了20个线程,总共有200万瓶啤酒。看看你能否找到丢失的代码,搞定以后干杯庆祝一下。
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
// 打印错误的函数
void error(char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}
int beers = 2000000;
void* drink_lots(void* param) {
int i;
for (i = 0; i < 100000; i++) {
beers = beers - 1;
}
return NULL;
}
int main() {
pthread_t threads[20];
int t;
printf("%i bottles of beer on the wall\n%i bottles of beer\n", beers,
beers);
// 创建20个线程来运行该函数
for (t = 0; t < 20; t++) {
//if (pthread_create(&threads[t], NULL, drink_lots, (void*)thread_no) == -1)
if (pthread_create(&threads[t], NULL, drink_lots, NULL) == -1)
error("无法创建线程");
}
// 等待所有线程结束
void* result;
for (t = 0; t < 20; t++) {
if (pthread_join(threads[t], &result) == -1)
error("无法回收线程");
}
printf("There are now %i bottles of beer on the wall\n", beers);
return 0;
}
运行结果:
结果显示,大多数情况下,代码中两个线程并没有把beers变量减为0!---原因就是:
多个线程同时访问同一个数据,就会导致线程安全问题,数据就会出错!
增设红绿灯---互斥锁
假设两辆车想要驶过一段羊肠小道。为了防止交通事故,你可以增设红绿灯,它可以防止两辆车同时访问共享资源。
如果想防止两个或多个线程访问共享数据资源,也可以采取相同的方法:增设红绿灯。这样两个线程就不能同时读取相同数据,并把它写回。
用来防止线程发生车祸的红绿灯就叫互斥锁,它们是把代码变为线程安全最简单的方法。
用互斥锁来管理交通
// 创建互斥锁
pthread_mutex_t a_lock = PTHREAD_MUTEX_INITIALIZER;
互斥锁必须对所有可能发生冲突的线程可见,也就是说它是一个全局变量。
PTHREAD_MUTEX_INITIALIZER 实际上是一个宏,当编译器看到它,就会插入创建互斥锁的代码。
pthread_mutex_lock(&a_lock);
/* 含有共享数据的代码从这里开始 */
....这里的代码是线程安全的
/* ... 代码结束了 */
pthread_mutex_unlock(&a_lock);
线程函数可以接收一个void指针作为参数,并返回一个void指针
值。通常你希望把某个整型值传给线程,并让它返回某个整型值,
一种方法是用 long ,因为它的大小和void指针相同,可以把它保
存在void指针变量中。
void* do_stuff(void* param)
{
long thread_no = (long)param; // 线程编号
printf("Thread number %ld\n", thread_no);
return (void*)(thread_no + 1);
}
用互斥锁来修改上面的beers.c代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
// 打印错误的函数
void error(char *msg) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(1);
}
// 创建互斥锁
pthread_mutex_t a_lock = PTHREAD_MUTEX_INITIALIZER;
int beers = 2000000;
void* drink_lots(void* param) {
// 线程函数可以接收一个void指针类型的参数,这里将参数转化为long类型
long thread_no = (long) param;
int i;
for (i = 0; i < 100000; i++) {
pthread_mutex_lock(&a_lock);
/* 含有共享数据的代码从这里开始 */
beers = beers - 1;
/* ... 同步代码结束了 */
pthread_mutex_unlock(&a_lock);
// 现在线程安全了
}
// 打印线程号
//printf("Thread number %ld :", thread_no);
printf("beers = %i\n", beers);
return NULL;
// 返回时将其类型转化为void指针
//return (void*)(thread_no + 1);
}
int main() {
pthread_t threads[20];
printf("%i bottles of beer on the wall\n%i bottles of beer\n", beers,
beers);
// 创建20个线程来运行该函数
int t;
for (t = 0; t < 20; t++) {
//if (pthread_create(&threads[t], NULL, drink_lots, NULL) == -1)
if (pthread_create(&threads[t], NULL, drink_lots, (void*) t) == -1)
error("无法创建线程");
}
// 等待所有线程结束
void* result;
for (t = 0; t < 20; t++) {
if (pthread_join(threads[t], &result) == -1)
error("无法回收线程");
//printf("Thread %ld returned %ld\n", t, (long)result);
}
printf("There are now %i bottles of beer on the wall\n", beers);
return 0;
}
运行结果:
可以看到,这下线程安全了!
问: 怎样设计高效的多线程程序?
答: 减少线程需要访问的共享数据的数量。如果线程无需访问很多共享数据,那么多个线程等一个线程的情况就很少出现,速度会大大提高。
问: 线程要比进程快?
答: 通常是这样,因为创建进程要比创建线程花更多时间。
问: 听说互斥锁会引发“死锁”,那是什么玩意儿?
答: 假设你有两个线程,它们都想得到互斥锁A和B。倘若第一个线程得到了A,第二个线程得到了B,这两个线程就会陷入死锁。因为第一个线程无法得到B,第二个线程无法得到A,它俩都停滞不前。
0 0
- HeadFirstC笔记_12 线程:平行世界
- Java学习笔记_12
- HeadFirstC笔记_1.C语言入门
- HeadFirstC笔记_13 十大遗漏知识点
- 平行世界产品分析|冷暖自知的世界
- 【题解】【欧拉图】罪世界-平行齿轮
- 杂谈 | 移动互联网创造的平行世界
- HeadFirstC笔记_2.存储器和指针:指向何方?
- HeadFirstC笔记_2.5 字符串:字符串原理
- HeadFirstC笔记_4 使用多个源文件:分而治之
- HeadFirstC笔记_6 数据结构与动态存储:牵线搭桥
- HeadFirstC笔记_7 高级函数:发挥函数的极限
- HeadFirstC笔记_8 静态库与动态库:热插拔代码
- HeadFirstC笔记_9 进程与系统调用:打破疆界
- HeadFirstC笔记_10 进程间通信:沟通的艺术
- HeadFirstC笔记_11 网络与套接字
- linux_学习笔记_12/3/2010
- JavaSE学习笔记_12:Java集合框架
- html 文字和图片实现自适应
- 管理文件权限和所有权
- vs2010工程路径不可以包含逗号“,”
- [Android] 仿网易新闻客户端分类排序
- mysql 远程连接数据库的二种方法
- HeadFirstC笔记_12 线程:平行世界
- 渲染流水线
- [Wondgirl]从零开始学React Native之image(六)
- 转 Maven的pom.xml介绍
- 4524: [Cqoi2016]伪光滑数
- 今天学习一下STM32的ADC(模拟/数字转化器)——IO口配置
- 常用到的JS 验证(包括例子)
- App测试中ios和Android的区别1
- RHEL/CentOS/Fedora各种源(EPEL、Remi、RPMForge、RPMFusion)配置 、YUM 源优先级插件:Yum Priorities